Merge branch '21894-diagnostics-dev-version-mismatch'
[arvados.git] / services / api / test / functional / arvados / v1 / groups_controller_test.rb
index 30ab89c7e2aa4527960e518cdf63a95bbaef4550..52ed140bae06f248eed340d5c4a430b743b04144 100644 (file)
@@ -6,12 +6,19 @@ require 'test_helper'
 
 class Arvados::V1::GroupsControllerTest < ActionController::TestCase
 
 
 class Arvados::V1::GroupsControllerTest < ActionController::TestCase
 
-  test "attempt to delete group without read or write access" do
+  test "attempt to delete group that cannot be seen" do
+    Rails.configuration.Users.RoleGroupsVisibleToAll = false
     authorize_with :active
     post :destroy, params: {id: groups(:empty_lonely_group).uuid}
     assert_response 404
   end
 
     authorize_with :active
     post :destroy, params: {id: groups(:empty_lonely_group).uuid}
     assert_response 404
   end
 
+  test "attempt to delete group without read or write access" do
+    authorize_with :active
+    post :destroy, params: {id: groups(:empty_lonely_group).uuid}
+    assert_response 403
+  end
+
   test "attempt to delete group without write access" do
     authorize_with :active
     post :destroy, params: {id: groups(:all_users).uuid}
   test "attempt to delete group without write access" do
     authorize_with :active
     post :destroy, params: {id: groups(:all_users).uuid}
@@ -29,8 +36,9 @@ class Arvados::V1::GroupsControllerTest < ActionController::TestCase
     end
     assert_includes group_uuids, groups(:aproject).uuid
     assert_includes group_uuids, groups(:asubproject).uuid
     end
     assert_includes group_uuids, groups(:aproject).uuid
     assert_includes group_uuids, groups(:asubproject).uuid
+    assert_includes group_uuids, groups(:private).uuid
     assert_not_includes group_uuids, groups(:system_group).uuid
     assert_not_includes group_uuids, groups(:system_group).uuid
-    assert_not_includes group_uuids, groups(:private).uuid
+    assert_not_includes group_uuids, groups(:private_and_can_read_foofile).uuid
   end
 
   test "get list of groups that are not projects" do
   end
 
   test "get list of groups that are not projects" do
@@ -44,8 +52,6 @@ class Arvados::V1::GroupsControllerTest < ActionController::TestCase
     end
     assert_not_includes group_uuids, groups(:aproject).uuid
     assert_not_includes group_uuids, groups(:asubproject).uuid
     end
     assert_not_includes group_uuids, groups(:aproject).uuid
     assert_not_includes group_uuids, groups(:asubproject).uuid
-    assert_includes group_uuids, groups(:private).uuid
-    assert_includes group_uuids, groups(:group_with_no_class).uuid
   end
 
   test "get list of groups with bogus group_class" do
   end
 
   test "get list of groups with bogus group_class" do
@@ -59,12 +65,12 @@ class Arvados::V1::GroupsControllerTest < ActionController::TestCase
     assert_equal 0, json_response['items_available']
   end
 
     assert_equal 0, json_response['items_available']
   end
 
-  def check_project_contents_response disabled_kinds=[]
+  def check_project_contents_response
     assert_response :success
     assert_operator 2, :<=, json_response['items_available']
     assert_operator 2, :<=, json_response['items'].count
     kinds = json_response['items'].collect { |i| i['kind'] }.uniq
     assert_response :success
     assert_operator 2, :<=, json_response['items_available']
     assert_operator 2, :<=, json_response['items'].count
     kinds = json_response['items'].collect { |i| i['kind'] }.uniq
-    expect_kinds = %w'arvados#group arvados#specimen arvados#pipelineTemplate arvados#job' - disabled_kinds
+    expect_kinds = %w'arvados#group'
     assert_equal expect_kinds, (expect_kinds & kinds)
 
     json_response['items'].each do |i|
     assert_equal expect_kinds, (expect_kinds & kinds)
 
     json_response['items'].each do |i|
@@ -73,10 +79,6 @@ class Arvados::V1::GroupsControllerTest < ActionController::TestCase
                "group#contents returned a non-project group")
       end
     end
                "group#contents returned a non-project group")
       end
     end
-
-    disabled_kinds.each do |d|
-      assert_equal true, !kinds.include?(d)
-    end
   end
 
   test 'get group-owned objects' do
   end
 
   test 'get group-owned objects' do
@@ -101,17 +103,17 @@ class Arvados::V1::GroupsControllerTest < ActionController::TestCase
     authorize_with :project_viewer
     get :contents, params: {
       format: :json,
     authorize_with :project_viewer
     get :contents, params: {
       format: :json,
-      filters: [['uuid', 'is_a', 'arvados#specimen']]
+      filters: [['uuid', 'is_a', 'arvados#collection']]
     }
     assert_response :success
     found_uuids = json_response['items'].collect { |i| i['uuid'] }
     }
     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|
+    [[:foo_collection_in_aproject, true],
+     [:baz_collection_name_in_asubproject, true],
+     [:collection_not_readable_by_active, false]].each do |collection_fixture, should_find|
       if should_find
       if should_find
-        assert_includes found_uuids, specimens(specimen_fixture).uuid, "did not find specimen fixture '#{specimen_fixture}'"
+        assert_includes found_uuids, collections(collection_fixture).uuid, "did not find collection fixture '#{collection_fixture}'"
       else
       else
-        refute_includes found_uuids, specimens(specimen_fixture).uuid, "found specimen fixture '#{specimen_fixture}'"
+        refute_includes found_uuids, collections(collection_fixture).uuid, "found collection fixture '#{collection_fixture}'"
       end
     end
   end
       end
     end
   end
@@ -144,8 +146,41 @@ class Arvados::V1::GroupsControllerTest < ActionController::TestCase
     }
     assert_response :success
     found_uuids = json_response['items'].collect { |i| i['uuid'] }
     }
     assert_response :success
     found_uuids = json_response['items'].collect { |i| i['uuid'] }
-    assert_includes found_uuids, specimens(:owned_by_active_user).uuid, "specimen did not appear in home project"
-    refute_includes found_uuids, specimens(:in_asubproject).uuid, "specimen appeared unexpectedly in home project"
+    assert_includes found_uuids, collections(:collection_owned_by_active).uuid, "collection did not appear in home project"
+    refute_includes found_uuids, collections(:foo_collection_in_aproject).uuid, "collection appeared unexpectedly in home project"
+  end
+
+  test "list collections in home project" do
+    authorize_with :active
+    get(:contents, params: {
+          format: :json,
+          filters: [
+            ['uuid', 'is_a', 'arvados#collection'],
+          ],
+          limit: 200,
+          id: users(:active).uuid,
+        })
+    assert_response :success
+    found_uuids = json_response['items'].collect { |i| i['uuid'] }
+    assert_includes found_uuids, collections(:collection_owned_by_active).uuid, "collection did not appear in home project"
+    refute_includes found_uuids, collections(:collection_owned_by_active_past_version_1).uuid, "collection appeared unexpectedly in home project"
+  end
+
+  test "list collections in home project, including old versions" do
+    authorize_with :active
+    get(:contents, params: {
+          format: :json,
+          include_old_versions: true,
+          filters: [
+            ['uuid', 'is_a', 'arvados#collection'],
+          ],
+          limit: 200,
+          id: users(:active).uuid,
+        })
+    assert_response :success
+    found_uuids = json_response['items'].collect { |i| i['uuid'] }
+    assert_includes found_uuids, collections(:collection_owned_by_active).uuid, "collection did not appear in home project"
+    assert_includes found_uuids, collections(:collection_owned_by_active_past_version_1).uuid, "old collection version did not appear in home project"
   end
 
   test "user with project read permission can see project collections" do
   end
 
   test "user with project read permission can see project collections" do
@@ -240,20 +275,20 @@ class Arvados::V1::GroupsControllerTest < ActionController::TestCase
 
   test "user with project read permission can't rename items in it" do
     authorize_with :project_viewer
 
   test "user with project read permission can't rename items in it" do
     authorize_with :project_viewer
-    @controller = Arvados::V1::LinksController.new
+    @controller = Arvados::V1::CollectionsController.new
     post :update, params: {
     post :update, params: {
-      id: jobs(:running).uuid,
+      id: collections(:collection_to_search_for_in_aproject).uuid,
       name: "Denied test name",
     }
     assert_includes(403..404, response.status)
   end
 
   test "user with project read permission can't remove items from it" do
       name: "Denied test name",
     }
     assert_includes(403..404, response.status)
   end
 
   test "user with project read permission can't remove items from it" do
-    @controller = Arvados::V1::PipelineTemplatesController.new
+    @controller = Arvados::V1::CollectionsController.new
     authorize_with :project_viewer
     post :update, params: {
     authorize_with :project_viewer
     post :update, params: {
-      id: pipeline_templates(:two_part).uuid,
-      pipeline_template: {
+      id: collections(:collection_to_search_for_in_aproject).uuid,
+      collection: {
         owner_uuid: users(:project_viewer).uuid,
       }
     }
         owner_uuid: users(:project_viewer).uuid,
       }
     }
@@ -291,6 +326,38 @@ class Arvados::V1::GroupsControllerTest < ActionController::TestCase
     assert_equal 0, json_response['items'].count
   end
 
     assert_equal 0, json_response['items'].count
   end
 
+  test 'get group-owned objects with select' do
+    authorize_with :active
+    get :contents, params: {
+      id: groups(:aproject).uuid,
+      limit: 100,
+      format: :json,
+      select: ["uuid", "storage_classes_desired"]
+    }
+    assert_response :success
+    assert_equal 6, json_response['items_available']
+    assert_equal 6, json_response['items'].count
+    json_response['items'].each do |item|
+      # Expect collections to have a storage_classes field, other items should not.
+      if item["kind"] == "arvados#collection"
+        assert !item["storage_classes_desired"].nil?
+      else
+        assert item["storage_classes_desired"].nil?
+      end
+    end
+  end
+
+  test 'get group-owned objects with invalid field in select' do
+    authorize_with :active
+    get :contents, params: {
+      id: groups(:aproject).uuid,
+      limit: 100,
+      format: :json,
+      select: ["uuid", "storage_classes_desire"]
+    }
+    assert_response 422
+  end
+
   test 'get group-owned objects with additional filter matching nothing' do
     authorize_with :active
     get :contents, params: {
   test 'get group-owned objects with additional filter matching nothing' do
     authorize_with :active
     get :contents, params: {
@@ -317,7 +384,7 @@ class Arvados::V1::GroupsControllerTest < ActionController::TestCase
     end
   end
 
     end
   end
 
-  test "Collection contents don't include manifest_text" do
+  test "Collection contents don't include manifest_text or unsigned_manifest_text" do
     authorize_with :active
     get :contents, params: {
       id: groups(:aproject).uuid,
     authorize_with :active
     get :contents, params: {
       id: groups(:aproject).uuid,
@@ -328,7 +395,9 @@ class Arvados::V1::GroupsControllerTest < ActionController::TestCase
     refute(json_response["items"].any? { |c| not c["portable_data_hash"] },
            "response included an item without a portable data hash")
     refute(json_response["items"].any? { |c| c.include?("manifest_text") },
     refute(json_response["items"].any? { |c| not c["portable_data_hash"] },
            "response included an item without a portable data hash")
     refute(json_response["items"].any? { |c| c.include?("manifest_text") },
-           "response included an item with a manifest text")
+           "response included an item with manifest_text")
+    refute(json_response["items"].any? { |c| c.include?("unsigned_manifest_text") },
+           "response included an item with unsigned_manifest_text")
   end
 
   test 'get writable_by list for owned group' do
   end
 
   test 'get writable_by list for owned group' do
@@ -401,15 +470,15 @@ class Arvados::V1::GroupsControllerTest < ActionController::TestCase
     assert_not_equal(new_project['uuid'],
                      groups(:aproject).uuid,
                      "create returned same uuid as existing project")
     assert_not_equal(new_project['uuid'],
                      groups(:aproject).uuid,
                      "create returned same uuid as existing project")
-    assert_match(/^A Project \(\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d\.\d{3}Z\)$/,
+    assert_match(/^A Project \(#{new_project['uuid'][-15..-1]}\)$/,
                  new_project['name'])
   end
 
   [
     [['owner_uuid', '!=', 'zzzzz-tpzed-xurymjxw79nv3jz'], 200,
                  new_project['name'])
   end
 
   [
     [['owner_uuid', '!=', 'zzzzz-tpzed-xurymjxw79nv3jz'], 200,
-        'zzzzz-d1hrv-subprojpipeline', 'zzzzz-d1hrv-1xfj6xkicf2muk2'],
-    [["pipeline_instances.state", "not in", ["Complete", "Failed"]], 200,
-        'zzzzz-d1hrv-1xfj6xkicf2muk2', 'zzzzz-d1hrv-i3e77t9z5y8j9cc'],
+        'zzzzz-j7d0g-publicfavorites', 'zzzzz-xvhdp-cr4queuedcontnr'],
+    [["container_requests.state", "not in", ["Final"]], 200,
+        'zzzzz-xvhdp-cr4queuedcontnr', 'zzzzz-xvhdp-cr4completedctr'],
     [['container_requests.requesting_container_uuid', '=', nil], 200,
         'zzzzz-xvhdp-cr4queuedcontnr', 'zzzzz-xvhdp-cr4requestercn2'],
     [['container_requests.no_such_column', '=', nil], 422],
     [['container_requests.requesting_container_uuid', '=', nil], 200,
         'zzzzz-xvhdp-cr4queuedcontnr', 'zzzzz-xvhdp-cr4requestercn2'],
     [['container_requests.no_such_column', '=', nil], 422],
@@ -430,24 +499,17 @@ class Arvados::V1::GroupsControllerTest < ActionController::TestCase
     end
   end
 
     end
   end
 
-  test 'get contents with jobs and pipeline instances disabled' do
-    Rails.configuration.API.DisabledAPIs = {'jobs.index'=>{}, 'pipeline_instances.index'=>{}}
-
-    authorize_with :active
-    get :contents, params: {
-      id: groups(:aproject).uuid,
-      format: :json,
-    }
-    check_project_contents_response %w'arvados#pipelineInstance arvados#job'
-  end
-
   test 'get contents with low max_index_database_read' do
     # Some result will certainly have at least 12 bytes in a
   test 'get contents with low max_index_database_read' do
     # Some result will certainly have at least 12 bytes in a
-    # restricted column
+    # restricted column.
+    #
+    # We cannot use collections.manifest_text to test this, because
+    # GroupsController refuses to select manifest_text, because
+    # controller doesn't sign manifests in a groups#contents response.
     Rails.configuration.API.MaxIndexDatabaseRead = 12
     authorize_with :active
     get :contents, params: {
     Rails.configuration.API.MaxIndexDatabaseRead = 12
     authorize_with :active
     get :contents, params: {
-          id: groups(:aproject).uuid,
+          uuid: users(:active).uuid,
           format: :json,
         }
     assert_response :success
           format: :json,
         }
     assert_response :success
@@ -503,11 +565,60 @@ class Arvados::V1::GroupsControllerTest < ActionController::TestCase
     assert_includes(owners, groups(:asubproject).uuid)
   end
 
     assert_includes(owners, groups(:asubproject).uuid)
   end
 
+  [:afiltergroup, :private_role].each do |grp|
+    test "delete non-project group #{grp}" do
+      authorize_with :admin
+      assert_not_nil Group.find_by_uuid(groups(grp).uuid)
+      assert !Group.find_by_uuid(groups(grp).uuid).is_trashed
+      post :destroy, params: {
+            id: groups(grp).uuid,
+            format: :json,
+          }
+      assert_response :success
+      # Should not be trashed
+      assert_nil Group.find_by_uuid(groups(grp).uuid)
+    end
+  end
+
+  [
+    [false, :inactive, :private_role, false],
+    [false, :spectator, :private_role, false],
+    [false, :admin, :private_role, true],
+    [true, :inactive, :private_role, false],
+    [true, :spectator, :private_role, true],
+    [true, :admin, :private_role, true],
+    # project (non-role) groups are invisible even when RoleGroupsVisibleToAll is true
+    [true, :inactive, :private, false],
+    [true, :spectator, :private, false],
+    [true, :admin, :private, true],
+  ].each do |visibleToAll, userFixture, groupFixture, visible|
+    test "with RoleGroupsVisibleToAll=#{visibleToAll}, #{groupFixture} group is #{visible ? '' : 'in'}visible to #{userFixture} user" do
+      Rails.configuration.Users.RoleGroupsVisibleToAll = visibleToAll
+      authorize_with userFixture
+      get :show, params: {id: groups(groupFixture).uuid, format: :json}
+      if visible
+        assert_response :success
+      else
+        assert_response 404
+      end
+    end
+  end
+
   ### trashed project tests ###
 
   ### trashed project tests ###
 
-  [:active, :admin].each do |auth|
+  #
+  # The structure is
+  #
+  # trashed_project         (zzzzz-j7d0g-trashedproject1)
+  #   trashed_subproject    (zzzzz-j7d0g-trashedproject2)
+  #   trashed_subproject3   (zzzzz-j7d0g-trashedproject3)
+  #   zzzzz-xvhdp-cr5trashedcontr
+
+  [:active,
+   :admin].each do |auth|
     # project: to query,    to untrash,    is visible, parent contents listing success
     # project: to query,    to untrash,    is visible, parent contents listing success
-    [[:trashed_project,     [],                 false, true],
+    [
+     [:trashed_project,     [],                 false, true],
      [:trashed_project,     [:trashed_project], true,  true],
      [:trashed_subproject,  [],                 false, false],
      [:trashed_subproject,  [:trashed_project], true,  true],
      [:trashed_project,     [:trashed_project], true,  true],
      [:trashed_subproject,  [],                 false, false],
      [:trashed_subproject,  [:trashed_project], true,  true],
@@ -677,7 +788,7 @@ class Arvados::V1::GroupsControllerTest < ActionController::TestCase
             ensure_unique_name: true
            }
       assert_response :success
             ensure_unique_name: true
            }
       assert_response :success
-      assert_match /^trashed subproject 3 \(\d{4}-\d\d-\d\d.*?Z\)$/, json_response['name']
+      assert_match /^trashed subproject 3 \(#{json_response['uuid'][-15..-1]}\)$/, json_response['name']
     end
 
     test "move trashed subproject to new owner #{auth}" do
     end
 
     test "move trashed subproject to new owner #{auth}" do
@@ -696,6 +807,28 @@ class Arvados::V1::GroupsControllerTest < ActionController::TestCase
     end
   end
 
     end
   end
 
+  # the group class overrides the destroy method. Make sure that the destroyed
+  # object is returned
+  [
+    {group_class: "project"},
+    {group_class: "role"},
+    {group_class: "filter", properties: {"filters":[]}},
+  ].each do |params|
+    test "destroy group #{params} returns object" do
+      authorize_with :active
+
+      group = Group.create!(params)
+
+      post :destroy, params: {
+            id: group.uuid,
+            format: :json,
+          }
+      assert_response :success
+      assert_not_nil json_response
+      assert_equal group.uuid, json_response["uuid"]
+    end
+  end
+
   test 'get shared owned by another user' do
     authorize_with :user_bar_in_sharing_group
 
   test 'get shared owned by another user' do
     authorize_with :user_bar_in_sharing_group
 
@@ -736,20 +869,23 @@ class Arvados::V1::GroupsControllerTest < ActionController::TestCase
     assert_equal 0, json_response['included'].length
   end
 
     assert_equal 0, json_response['included'].length
   end
 
-  test 'get shared, owned by non-project' do
+  test 'get shared, add permission link' do
     authorize_with :user_bar_in_sharing_group
 
     act_as_system_user 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)
+      Link.create!(tail_uuid: groups(:group_for_sharing_tests).uuid,
+                   head_uuid: groups(:project_owned_by_foo).uuid,
+                   link_class: 'permission',
+                   name: 'can_manage')
     end
 
     get :shared, params: {:filters => [["group_class", "=", "project"]], :include => "owner_uuid"}
 
     assert_equal 1, json_response['items'].length
     end
 
     get :shared, params: {: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 groups(:project_owned_by_foo).uuid, json_response['items'][0]["uuid"]
 
     assert_equal 1, json_response['included'].length
 
     assert_equal 1, json_response['included'].length
-    assert_equal json_response['included'][0]["uuid"], groups(:group_for_sharing_tests).uuid
+    assert_equal users(:user_foo_in_sharing_group).uuid, json_response['included'][0]["uuid"]
   end
 
   ### contents with exclude_home_project
   end
 
   ### contents with exclude_home_project
@@ -800,20 +936,24 @@ class Arvados::V1::GroupsControllerTest < ActionController::TestCase
     assert_equal 0, json_response['included'].length
   end
 
     assert_equal 0, json_response['included'].length
   end
 
-  test 'contents, exclude home, owned by non-project' do
+  test 'contents, exclude home, add permission link' do
     authorize_with :user_bar_in_sharing_group
 
     act_as_system_user 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)
+      Link.create!(tail_uuid: groups(:group_for_sharing_tests).uuid,
+                   head_uuid: groups(:project_owned_by_foo).uuid,
+                   link_class: 'permission',
+                   name: 'can_manage')
     end
 
     get :contents, params: {:include => "owner_uuid", :exclude_home_project => true}
     end
 
     get :contents, params: {:include => "owner_uuid", :exclude_home_project => true}
+    assert_response 200
 
     assert_equal 1, json_response['items'].length
 
     assert_equal 1, json_response['items'].length
-    assert_equal json_response['items'][0]["uuid"], groups(:project_owned_by_foo).uuid
+    assert_equal groups(:project_owned_by_foo).uuid, json_response['items'][0]["uuid"]
 
     assert_equal 1, json_response['included'].length
 
     assert_equal 1, json_response['included'].length
-    assert_equal json_response['included'][0]["uuid"], groups(:group_for_sharing_tests).uuid
+    assert_equal users(:user_foo_in_sharing_group).uuid, json_response['included'][0]["uuid"]
   end
 
   test 'contents, exclude home, with parent specified' do
   end
 
   test 'contents, exclude home, with parent specified' do
@@ -823,4 +963,60 @@ class Arvados::V1::GroupsControllerTest < ActionController::TestCase
 
     assert_response 422
   end
 
     assert_response 422
   end
+
+  [[false, 'owner_uuid'],
+   [false, []],
+   [false, ''],
+   [true, 'container_uuid'],
+   [true, ['container_uuid']],
+   [true, ['owner_uuid', 'container_uuid'], ['uuid', 'container_uuid', 'state', 'output']],
+  ].each do |check_container_included, include_param, select_param|
+    test "contents, include=#{include_param.inspect}" do
+      authorize_with :active
+      get :contents, params: {
+            :id => users(:active).uuid,
+            :include => include_param,
+            :limit => 1000,
+            :select => select_param,
+          }
+      assert_response 200
+      if include_param.empty?
+        assert_equal false, json_response.include?('included')
+        return
+      end
+      incl = {}
+      json_response['included'].andand.each do |ctr|
+        incl[ctr['uuid']] = ctr
+      end
+      next if !check_container_included
+      checked_crs = 0
+      json_response['items'].each do |item|
+        next if !item['container_uuid']
+        assert_equal item['container_uuid'], incl[item['container_uuid']]['uuid']
+        assert_not_empty incl[item['container_uuid']]['state']
+        checked_crs += 1
+      end
+      assert_operator 0, :<, checked_crs
+    end
+  end
+
+  test "include_trash does not return trash inside frozen project" do
+    authorize_with :active
+    trashtime = Time.now - 1.second
+    outerproj = Group.create!(group_class: 'project')
+    innerproj = Group.create!(group_class: 'project', owner_uuid: outerproj.uuid)
+    innercoll = Collection.create!(name: 'inner-not-trashed', owner_uuid: innerproj.uuid)
+    innertrash = Collection.create!(name: 'inner-trashed', owner_uuid: innerproj.uuid, trash_at: trashtime)
+    innertrashproj = Group.create!(group_class: 'project', name: 'inner-trashed-proj', owner_uuid: innerproj.uuid, trash_at: trashtime)
+    outertrash = Collection.create!(name: 'outer-trashed', owner_uuid: outerproj.uuid, trash_at: trashtime)
+    innerproj.update!(frozen_by_uuid: users(:active).uuid)
+    get :contents, params: {id: outerproj.uuid, include_trash: true, recursive: true}
+    assert_response :success
+    uuids = json_response['items'].collect { |item| item['uuid'] }
+    assert_includes uuids, outertrash.uuid
+    assert_includes uuids, innerproj.uuid
+    assert_includes uuids, innercoll.uuid
+    refute_includes uuids, innertrash.uuid
+    refute_includes uuids, innertrashproj.uuid
+  end
 end
 end