21541: Code cleanup and additional memory usage improvements
[arvados.git] / services / api / test / unit / collection_test.rb
index a1008eec4d18d6a8039f3dcdd58e85ab6fc0f02a..f3b48dbf70a60b196ef635469bb8b1a6b6e59100 100644 (file)
@@ -3,7 +3,7 @@
 # SPDX-License-Identifier: AGPL-3.0
 
 require 'test_helper'
 # SPDX-License-Identifier: AGPL-3.0
 
 require 'test_helper'
-require 'sweep_trashed_objects'
+require 'fix_collection_versions_timestamps'
 
 class CollectionTest < ActiveSupport::TestCase
   include DbCurrentTime
 
 class CollectionTest < ActiveSupport::TestCase
   include DbCurrentTime
@@ -60,6 +60,56 @@ class CollectionTest < ActiveSupport::TestCase
     end
   end
 
     end
   end
 
+  [
+    [". d41d8cd98f00b204e9800998ecf8427e 0:34:foo.txt\n", 1, 34],
+    [". d41d8cd98f00b204e9800998ecf8427e 0:34:foo.txt 0:30:foo.txt 0:30:foo1.txt 0:30:foo2.txt 0:30:foo3.txt 0:30:foo4.txt\n", 5, 184],
+    [". d41d8cd98f00b204e9800998ecf8427e 0:0:.\n", 0, 0]
+  ].each do |manifest, count, size|
+    test "file stats on create collection with #{manifest}" do
+      act_as_system_user do
+        c = Collection.create(manifest_text: manifest)
+        assert_equal count, c.file_count
+        assert_equal size, c.file_size_total
+      end
+    end
+  end
+
+  test "file stats cannot be changed unless through manifest change" do
+    act_as_system_user do
+      # Direct changes to file stats should be ignored
+      c = Collection.create(manifest_text: ". d41d8cd98f00b204e9800998ecf8427e 0:34:foo.txt\n")
+      c.file_count = 6
+      c.file_size_total = 30
+      assert c.valid?
+      assert_equal 1, c.file_count
+      assert_equal 34, c.file_size_total
+
+      # File stats specified on create should be ignored and overwritten
+      c = Collection.create(manifest_text: ". d41d8cd98f00b204e9800998ecf8427e 0:34:foo.txt\n", file_count: 10, file_size_total: 10)
+      assert c.valid?
+      assert_equal 1, c.file_count
+      assert_equal 34, c.file_size_total
+
+      # Updating the manifest should change file stats
+      c.update(manifest_text: ". d41d8cd98f00b204e9800998ecf8427e 0:34:foo.txt 0:34:foo2.txt\n")
+      assert c.valid?
+      assert_equal 2, c.file_count
+      assert_equal 68, c.file_size_total
+
+      # Updating file stats and the manifest should use manifest values
+      c.update(manifest_text: ". d41d8cd98f00b204e9800998ecf8427e 0:34:foo.txt\n", file_count:10, file_size_total: 10)
+      assert c.valid?
+      assert_equal 1, c.file_count
+      assert_equal 34, c.file_size_total
+
+      # Updating just the file stats should be ignored
+      c.update(file_count: 10, file_size_total: 10)
+      assert c.valid?
+      assert_equal 1, c.file_count
+      assert_equal 34, c.file_size_total
+    end
+  end
+
   [
     nil,
     "",
   [
     nil,
     "",
@@ -107,8 +157,8 @@ class CollectionTest < ActiveSupport::TestCase
   end
 
   test "auto-create version after idle setting" do
   end
 
   test "auto-create version after idle setting" do
-    Rails.configuration.collection_versioning = true
-    Rails.configuration.preserve_version_if_idle = 600 # 10 minutes
+    Rails.configuration.Collections.CollectionVersioning = true
+    Rails.configuration.Collections.PreserveVersionIfIdle = 600 # 10 minutes
     act_as_user users(:active) do
       # Set up initial collection
       c = create_collection 'foo', Encoding::US_ASCII
     act_as_user users(:active) do
       # Set up initial collection
       c = create_collection 'foo', Encoding::US_ASCII
@@ -116,7 +166,7 @@ class CollectionTest < ActiveSupport::TestCase
       assert_equal 1, c.version
       assert_equal false, c.preserve_version
       # Make a versionable update, it shouldn't create a new version yet
       assert_equal 1, c.version
       assert_equal false, c.preserve_version
       # Make a versionable update, it shouldn't create a new version yet
-      c.update_attributes!({'name' => 'bar'})
+      c.update!({'name' => 'bar'})
       c.reload
       assert_equal 'bar', c.name
       assert_equal 1, c.version
       c.reload
       assert_equal 'bar', c.name
       assert_equal 1, c.version
@@ -125,21 +175,38 @@ class CollectionTest < ActiveSupport::TestCase
       c.update_column('modified_at', fifteen_min_ago) # Update without validations/callbacks
       c.reload
       assert_equal fifteen_min_ago.to_i, c.modified_at.to_i
       c.update_column('modified_at', fifteen_min_ago) # Update without validations/callbacks
       c.reload
       assert_equal fifteen_min_ago.to_i, c.modified_at.to_i
-      c.update_attributes!({'name' => 'baz'})
+      c.update!({'name' => 'baz'})
       c.reload
       assert_equal 'baz', c.name
       assert_equal 2, c.version
       # Make another update, no new version should be created
       c.reload
       assert_equal 'baz', c.name
       assert_equal 2, c.version
       # Make another update, no new version should be created
-      c.update_attributes!({'name' => 'foobar'})
+      c.update!({'name' => 'foobar'})
       c.reload
       assert_equal 'foobar', c.name
       assert_equal 2, c.version
       c.reload
       assert_equal 'foobar', c.name
       assert_equal 2, c.version
+      # Simulate a keep-balance run and trigger a new versionable update
+      # This tests bug #18005
+      assert_nil c.replication_confirmed
+      assert_nil c.replication_confirmed_at
+      # Updates without validations/callbacks
+      c.update_column('modified_at', fifteen_min_ago)
+      c.update_column('replication_confirmed_at', Time.now)
+      c.update_column('replication_confirmed', 2)
+      c.reload
+      assert_equal fifteen_min_ago.to_i, c.modified_at.to_i
+      assert_not_nil c.replication_confirmed_at
+      assert_not_nil c.replication_confirmed
+      # Make the versionable update
+      c.update!({'name' => 'foobarbaz'})
+      c.reload
+      assert_equal 'foobarbaz', c.name
+      assert_equal 3, c.version
     end
   end
 
     end
   end
 
-  test "preserve_version=false assignment is ignored while being true and not producing a new version" do
-    Rails.configuration.collection_versioning = true
-    Rails.configuration.preserve_version_if_idle = 3600
+  test "preserve_version updates" do
+    Rails.configuration.Collections.CollectionVersioning = true
+    Rails.configuration.Collections.PreserveVersionIfIdle = -1 # disabled
     act_as_user users(:active) do
       # Set up initial collection
       c = create_collection 'foo', Encoding::US_ASCII
     act_as_user users(:active) do
       # Set up initial collection
       c = create_collection 'foo', Encoding::US_ASCII
@@ -147,64 +214,179 @@ class CollectionTest < ActiveSupport::TestCase
       assert_equal 1, c.version
       assert_equal false, c.preserve_version
       # This update shouldn't produce a new version, as the idle time is not up
       assert_equal 1, c.version
       assert_equal false, c.preserve_version
       # This update shouldn't produce a new version, as the idle time is not up
-      c.update_attributes!({
-        'name' => 'bar',
-        'preserve_version' => true
+      c.update!({
+        'name' => 'bar'
       })
       c.reload
       assert_equal 1, c.version
       assert_equal 'bar', c.name
       })
       c.reload
       assert_equal 1, c.version
       assert_equal 'bar', c.name
+      assert_equal false, c.preserve_version
+      # This update should produce a new version, even if the idle time is not up
+      # and also keep the preserve_version=true flag to persist it.
+      c.update!({
+        'name' => 'baz',
+        'preserve_version' => true
+      })
+      c.reload
+      assert_equal 2, c.version
+      assert_equal 'baz', c.name
       assert_equal true, c.preserve_version
       # Make sure preserve_version is not disabled after being enabled, unless
       # a new version is created.
       assert_equal true, c.preserve_version
       # Make sure preserve_version is not disabled after being enabled, unless
       # a new version is created.
-      c.update_attributes!({
+      # This is a non-versionable update
+      c.update!({
         'preserve_version' => false,
         'replication_desired' => 2
       })
       c.reload
         'preserve_version' => false,
         'replication_desired' => 2
       })
       c.reload
-      assert_equal 1, c.version
+      assert_equal 2, c.version
       assert_equal 2, c.replication_desired
       assert_equal true, c.preserve_version
       assert_equal 2, c.replication_desired
       assert_equal true, c.preserve_version
-      c.update_attributes!({'name' => 'foobar'})
+      # This is a versionable update
+      c.update!({
+        'preserve_version' => false,
+        'name' => 'foobar'
+      })
       c.reload
       c.reload
-      assert_equal 2, c.version
+      assert_equal 3, c.version
       assert_equal false, c.preserve_version
       assert_equal 'foobar', c.name
       assert_equal false, c.preserve_version
       assert_equal 'foobar', c.name
+      # Flipping only 'preserve_version' to true doesn't create a new version
+      c.update!({'preserve_version' => true})
+      c.reload
+      assert_equal 3, c.version
+      assert_equal true, c.preserve_version
+    end
+  end
+
+  test "preserve_version updates don't change modified_at timestamp" do
+    act_as_user users(:active) do
+      c = create_collection 'foo', Encoding::US_ASCII
+      assert c.valid?
+      assert_equal false, c.preserve_version
+      modified_at = c.modified_at.to_f
+      c.update!({'preserve_version' => true})
+      c.reload
+      assert_equal true, c.preserve_version
+      assert_equal modified_at, c.modified_at.to_f,
+        'preserve_version updates should not trigger modified_at changes'
+    end
+  end
+
+  [
+    ['version', 10],
+    ['current_version_uuid', 'zzzzz-4zz18-bv31uwvy3neko21'],
+  ].each do |name, new_value|
+    test "'#{name}' updates on current version collections are not allowed" do
+      act_as_user users(:active) do
+        # Set up initial collection
+        c = create_collection 'foo', Encoding::US_ASCII
+        assert c.valid?
+        assert_equal 1, c.version
+
+        assert_raises(ActiveRecord::RecordInvalid) do
+          c.update!({
+            name => new_value
+          })
+        end
+      end
     end
   end
 
   test "uuid updates on current version make older versions update their pointers" do
     end
   end
 
   test "uuid updates on current version make older versions update their pointers" do
-    Rails.configuration.collection_versioning = true
-    Rails.configuration.preserve_version_if_idle = 0
+    Rails.configuration.Collections.CollectionVersioning = true
+    Rails.configuration.Collections.PreserveVersionIfIdle = 0
     act_as_system_user do
       # Set up initial collection
       c = create_collection 'foo', Encoding::US_ASCII
       assert c.valid?
       assert_equal 1, c.version
       # Make changes so that a new version is created
     act_as_system_user do
       # Set up initial collection
       c = create_collection 'foo', Encoding::US_ASCII
       assert c.valid?
       assert_equal 1, c.version
       # Make changes so that a new version is created
-      c.update_attributes!({'name' => 'bar'})
+      c.update!({'name' => 'bar'})
       c.reload
       assert_equal 2, c.version
       assert_equal 2, Collection.where(current_version_uuid: c.uuid).count
       new_uuid = 'zzzzz-4zz18-somefakeuuidnow'
       assert_empty Collection.where(uuid: new_uuid)
       # Update UUID on current version, check that both collections point to it
       c.reload
       assert_equal 2, c.version
       assert_equal 2, Collection.where(current_version_uuid: c.uuid).count
       new_uuid = 'zzzzz-4zz18-somefakeuuidnow'
       assert_empty Collection.where(uuid: new_uuid)
       # Update UUID on current version, check that both collections point to it
-      c.update_attributes!({'uuid' => new_uuid})
+      c.update!({'uuid' => new_uuid})
       c.reload
       assert_equal new_uuid, c.uuid
       assert_equal 2, Collection.where(current_version_uuid: new_uuid).count
     end
   end
 
       c.reload
       assert_equal new_uuid, c.uuid
       assert_equal 2, Collection.where(current_version_uuid: new_uuid).count
     end
   end
 
+  # This test exposes a bug related to JSONB attributes, see #15725.
+  test "recently loaded collection shouldn't list changed attributes" do
+    col = Collection.where("properties != '{}'::jsonb").limit(1).first
+    refute col.properties_changed?, 'Properties field should not be seen as changed'
+  end
+
+  [
+    [
+      true,
+      {'foo'=>'bar', 'lst'=>[1, 3, 5, 7], 'hsh'=>{'baz'=>'qux', 'foobar'=>true, 'hsh'=>{'nested'=>true}}, 'delete_at'=>nil},
+      {:foo=>:bar, :lst=>[1, 3, 5, 7], :hsh=>{'baz'=>'qux', :foobar=>true, 'hsh'=>{:nested=>true}}, :delete_at=>nil},
+    ],
+    [
+      true,
+      {'foo'=>'bar', 'lst'=>[1, 3, 5, 7], 'hsh'=>{'baz'=>'qux', 'foobar'=>true, 'hsh'=>{'nested'=>true}}, 'delete_at'=>nil},
+      {'delete_at'=>nil, 'foo'=>'bar', 'lst'=>[1, 3, 5, 7], 'hsh'=>{'baz'=>'qux', 'foobar'=>true, 'hsh'=>{'nested'=>true}}},
+    ],
+    [
+      true,
+      {'foo'=>'bar', 'lst'=>[1, 3, 5, 7], 'hsh'=>{'baz'=>'qux', 'foobar'=>true, 'hsh'=>{'nested'=>true}}, 'delete_at'=>nil},
+      {'delete_at'=>nil, 'foo'=>'bar', 'lst'=>[1, 3, 5, 7], 'hsh'=>{'foobar'=>true, 'hsh'=>{'nested'=>true}, 'baz'=>'qux'}},
+    ],
+    [
+      false,
+      {'foo'=>'bar', 'lst'=>[1, 3, 5, 7], 'hsh'=>{'baz'=>'qux', 'foobar'=>true, 'hsh'=>{'nested'=>true}}, 'delete_at'=>nil},
+      {'foo'=>'bar', 'lst'=>[1, 3, 5, 42], 'hsh'=>{'baz'=>'qux', 'foobar'=>true, 'hsh'=>{'nested'=>true}}, 'delete_at'=>nil},
+    ],
+    [
+      false,
+      {'foo'=>'bar', 'lst'=>[1, 3, 5, 7], 'hsh'=>{'baz'=>'qux', 'foobar'=>true, 'hsh'=>{'nested'=>true}}, 'delete_at'=>nil},
+      {'foo'=>'bar', 'lst'=>[1, 3, 7, 5], 'hsh'=>{'baz'=>'qux', 'foobar'=>true, 'hsh'=>{'nested'=>true}}, 'delete_at'=>nil},
+    ],
+    [
+      false,
+      {'foo'=>'bar', 'lst'=>[1, 3, 5, 7], 'hsh'=>{'baz'=>'qux', 'foobar'=>true, 'hsh'=>{'nested'=>true}}, 'delete_at'=>nil},
+      {'foo'=>'bar', 'lst'=>[1, 3, 5, 7], 'hsh'=>{'baz'=>'qux', 'foobar'=>true, 'hsh'=>{'nested'=>false}}, 'delete_at'=>nil},
+    ],
+    [
+      false,
+      {'foo'=>'bar', 'lst'=>[1, 3, 5, 7], 'hsh'=>{'baz'=>'qux', 'foobar'=>true, 'hsh'=>{'nested'=>true}}, 'delete_at'=>nil},
+      {'foo'=>'bar', 'lst'=>[1, 3, 5, 7], 'hsh'=>{'baz'=>'qux', 'foobar'=>true, 'hsh'=>{'nested'=>true}}, 'delete_at'=>1234567890},
+    ],
+  ].each do |should_be_equal, value_1, value_2|
+    test "JSONB properties #{value_1} is#{should_be_equal ? '' : ' not'} equal to #{value_2}" do
+      act_as_user users(:active) do
+        # Set up initial collection
+        c = create_collection 'foo', Encoding::US_ASCII
+        assert c.valid?
+        c.update!({'properties' => value_1})
+        c.reload
+        assert c.changes.keys.empty?
+        c.properties = value_2
+        if should_be_equal
+          assert c.changes.keys.empty?, "Properties #{value_1.inspect} should be equal to #{value_2.inspect}"
+        else
+          refute c.changes.keys.empty?, "Properties #{value_1.inspect} should not be equal to #{value_2.inspect}"
+        end
+      end
+    end
+  end
+
   test "older versions' modified_at indicate when they're created" do
   test "older versions' modified_at indicate when they're created" do
-    Rails.configuration.collection_versioning = true
-    Rails.configuration.preserve_version_if_idle = 0
+    Rails.configuration.Collections.CollectionVersioning = true
+    Rails.configuration.Collections.PreserveVersionIfIdle = 0
     act_as_user users(:active) do
       # Set up initial collection
       c = create_collection 'foo', Encoding::US_ASCII
       assert c.valid?
     act_as_user users(:active) do
       # Set up initial collection
       c = create_collection 'foo', Encoding::US_ASCII
       assert c.valid?
+      original_version_modified_at = c.modified_at.to_f
       # Make changes so that a new version is created
       # Make changes so that a new version is created
-      c.update_attributes!({'name' => 'bar'})
+      c.update!({'name' => 'bar'})
       c.reload
       assert_equal 2, c.version
       # Get the old version
       c.reload
       assert_equal 2, c.version
       # Get the old version
@@ -213,14 +395,12 @@ class CollectionTest < ActiveSupport::TestCase
 
       version_creation_datetime = c_old.modified_at.to_f
       assert_equal c.created_at.to_f, c_old.created_at.to_f
 
       version_creation_datetime = c_old.modified_at.to_f
       assert_equal c.created_at.to_f, c_old.created_at.to_f
-      # Current version is updated just a few milliseconds before the version is
-      # saved on the database.
-      assert_operator c.modified_at.to_f, :<, version_creation_datetime
+      assert_equal original_version_modified_at, version_creation_datetime
 
       # Make update on current version so old version get the attribute synced;
       # its modified_at should not change.
       new_replication = 3
 
       # Make update on current version so old version get the attribute synced;
       # its modified_at should not change.
       new_replication = 3
-      c.update_attributes!({'replication_desired' => new_replication})
+      c.update!({'replication_desired' => new_replication})
       c.reload
       assert_equal new_replication, c.replication_desired
       c_old.reload
       c.reload
       assert_equal new_replication, c.replication_desired
       c_old.reload
@@ -230,40 +410,56 @@ class CollectionTest < ActiveSupport::TestCase
     end
   end
 
     end
   end
 
-  test "older versions should no be directly updatable" do
-    Rails.configuration.collection_versioning = true
-    Rails.configuration.preserve_version_if_idle = 0
-    act_as_user users(:active) do
+  # Bug #17152 - This test relies on fixtures simulating the problem.
+  test "migration fixing collection versions' modified_at timestamps" do
+    versioned_collection_fixtures = [
+      collections(:w_a_z_file).uuid,
+      collections(:collection_owned_by_active).uuid
+    ]
+    versioned_collection_fixtures.each do |uuid|
+      cols = Collection.where(current_version_uuid: uuid).order(version: :desc)
+      assert_equal cols.size, 2
+      # cols[0] -> head version // cols[1] -> old version
+      assert_operator (cols[0].modified_at.to_f - cols[1].modified_at.to_f), :==, 0
+      assert cols[1].modified_at != cols[1].created_at
+    end
+    fix_collection_versions_timestamps
+    versioned_collection_fixtures.each do |uuid|
+      cols = Collection.where(current_version_uuid: uuid).order(version: :desc)
+      assert_equal cols.size, 2
+      # cols[0] -> head version // cols[1] -> old version
+      assert_operator (cols[0].modified_at.to_f - cols[1].modified_at.to_f), :>, 1
+      assert_operator cols[1].modified_at, :==, cols[1].created_at
+    end
+  end
+
+  test "past versions should not be directly updatable" do
+    Rails.configuration.Collections.CollectionVersioning = true
+    Rails.configuration.Collections.PreserveVersionIfIdle = 0
+    act_as_system_user do
       # Set up initial collection
       c = create_collection 'foo', Encoding::US_ASCII
       assert c.valid?
       # Make changes so that a new version is created
       # Set up initial collection
       c = create_collection 'foo', Encoding::US_ASCII
       assert c.valid?
       # Make changes so that a new version is created
-      c.update_attributes!({'name' => 'bar'})
+      c.update!({'name' => 'bar'})
       c.reload
       assert_equal 2, c.version
       # Get the old version
       c_old = Collection.where(current_version_uuid: c.uuid, version: 1).first
       assert_not_nil c_old
       # With collection versioning still being enabled, try to update
       c.reload
       assert_equal 2, c.version
       # Get the old version
       c_old = Collection.where(current_version_uuid: c.uuid, version: 1).first
       assert_not_nil c_old
       # With collection versioning still being enabled, try to update
-      assert_raises ArvadosModel::PermissionDeniedError do
-        c_old.update_attributes(name: 'this was foo')
-      end
+      c_old.name = 'this was foo'
+      assert c_old.invalid?
       c_old.reload
       c_old.reload
-      assert_equal 'foo', c_old.name
       # Try to fool the validator attempting to make c_old to look like a
       # current version, it should also fail.
       # Try to fool the validator attempting to make c_old to look like a
       # current version, it should also fail.
-      assert_raises ArvadosModel::PermissionDeniedError do
-        c_old.update_attributes(current_version_uuid: c_old.uuid)
-      end
+      c_old.current_version_uuid = c_old.uuid
+      assert c_old.invalid?
       c_old.reload
       c_old.reload
-      assert_equal c.uuid, c_old.current_version_uuid
       # Now disable collection versioning, it should behave the same way
       # Now disable collection versioning, it should behave the same way
-      Rails.configuration.collection_versioning = false
-      assert_raises ArvadosModel::PermissionDeniedError do
-        c_old.update_attributes(name: 'this was foo')
-      end
-      c_old.reload
-      assert_equal 'foo', c_old.name
+      Rails.configuration.Collections.CollectionVersioning = false
+      c_old.name = 'this was foo'
+      assert c_old.invalid?
     end
   end
 
     end
   end
 
@@ -271,11 +467,10 @@ class CollectionTest < ActiveSupport::TestCase
     ['owner_uuid', 'zzzzz-tpzed-d9tiejq69daie8f', 'zzzzz-tpzed-xurymjxw79nv3jz'],
     ['replication_desired', 2, 3],
     ['storage_classes_desired', ['hot'], ['archive']],
     ['owner_uuid', 'zzzzz-tpzed-d9tiejq69daie8f', 'zzzzz-tpzed-xurymjxw79nv3jz'],
     ['replication_desired', 2, 3],
     ['storage_classes_desired', ['hot'], ['archive']],
-    ['is_trashed', true, false],
   ].each do |attr, first_val, second_val|
     test "sync #{attr} with older versions" do
   ].each do |attr, first_val, second_val|
     test "sync #{attr} with older versions" do
-      Rails.configuration.collection_versioning = true
-      Rails.configuration.preserve_version_if_idle = 0
+      Rails.configuration.Collections.CollectionVersioning = true
+      Rails.configuration.Collections.PreserveVersionIfIdle = 0
       act_as_system_user do
         # Set up initial collection
         c = create_collection 'foo', Encoding::US_ASCII
       act_as_system_user do
         # Set up initial collection
         c = create_collection 'foo', Encoding::US_ASCII
@@ -284,7 +479,7 @@ class CollectionTest < ActiveSupport::TestCase
         assert_not_equal first_val, c.attributes[attr]
         # Make changes so that a new version is created and a synced field is
         # updated on both
         assert_not_equal first_val, c.attributes[attr]
         # Make changes so that a new version is created and a synced field is
         # updated on both
-        c.update_attributes!({'name' => 'bar', attr => first_val})
+        c.update!({'name' => 'bar', attr => first_val})
         c.reload
         assert_equal 2, c.version
         assert_equal first_val, c.attributes[attr]
         c.reload
         assert_equal 2, c.version
         assert_equal first_val, c.attributes[attr]
@@ -292,7 +487,7 @@ class CollectionTest < ActiveSupport::TestCase
         assert_equal first_val, Collection.where(current_version_uuid: c.uuid, version: 1).first.attributes[attr]
         # Only make an update on the same synced field & check that the previously
         # created version also gets it.
         assert_equal first_val, Collection.where(current_version_uuid: c.uuid, version: 1).first.attributes[attr]
         # Only make an update on the same synced field & check that the previously
         # created version also gets it.
-        c.update_attributes!({attr => second_val})
+        c.update!({attr => second_val})
         c.reload
         assert_equal 2, c.version
         assert_equal second_val, c.attributes[attr]
         c.reload
         assert_equal 2, c.version
         assert_equal second_val, c.attributes[attr]
@@ -316,8 +511,8 @@ class CollectionTest < ActiveSupport::TestCase
     [false, 'replication_desired', 5, false],
   ].each do |versioning, attr, val, new_version_expected|
     test "update #{attr} with versioning #{versioning ? '' : 'not '}enabled should #{new_version_expected ? '' : 'not '}create a new version" do
     [false, 'replication_desired', 5, false],
   ].each do |versioning, attr, val, new_version_expected|
     test "update #{attr} with versioning #{versioning ? '' : 'not '}enabled should #{new_version_expected ? '' : 'not '}create a new version" do
-      Rails.configuration.collection_versioning = versioning
-      Rails.configuration.preserve_version_if_idle = 0
+      Rails.configuration.Collections.CollectionVersioning = versioning
+      Rails.configuration.Collections.PreserveVersionIfIdle = 0
       act_as_user users(:active) do
         # Create initial collection
         c = create_collection 'foo', Encoding::US_ASCII
       act_as_user users(:active) do
         # Create initial collection
         c = create_collection 'foo', Encoding::US_ASCII
@@ -330,7 +525,7 @@ class CollectionTest < ActiveSupport::TestCase
 
         # Update attribute and check if version number should be incremented
         old_value = c.attributes[attr]
 
         # Update attribute and check if version number should be incremented
         old_value = c.attributes[attr]
-        c.update_attributes!({attr => val})
+        c.update!({attr => val})
         assert_equal new_version_expected, c.version == 2
         assert_equal val, c.attributes[attr]
 
         assert_equal new_version_expected, c.version == 2
         assert_equal val, c.attributes[attr]
 
@@ -350,9 +545,34 @@ class CollectionTest < ActiveSupport::TestCase
     end
   end
 
     end
   end
 
+  test 'current_version_uuid is ignored during update' do
+    Rails.configuration.Collections.CollectionVersioning = true
+    Rails.configuration.Collections.PreserveVersionIfIdle = 0
+    act_as_user users(:active) do
+      # Create 1st collection
+      col1 = create_collection 'foo', Encoding::US_ASCII
+      assert col1.valid?
+      assert_equal 1, col1.version
+
+      # Create 2nd collection, update it so it becomes version:2
+      # (to avoid unique index violation)
+      col2 = create_collection 'bar', Encoding::US_ASCII
+      assert col2.valid?
+      assert_equal 1, col2.version
+      col2.update({name: 'baz'})
+      assert_equal 2, col2.version
+
+      # Try to make col2 a past version of col1. It shouldn't be possible
+      col2.update({current_version_uuid: col1.uuid})
+      assert col2.invalid?
+      col2.reload
+      assert_not_equal col1.uuid, col2.current_version_uuid
+    end
+  end
+
   test 'with versioning enabled, simultaneous updates increment version correctly' do
   test 'with versioning enabled, simultaneous updates increment version correctly' do
-    Rails.configuration.collection_versioning = true
-    Rails.configuration.preserve_version_if_idle = 0
+    Rails.configuration.Collections.CollectionVersioning = true
+    Rails.configuration.Collections.PreserveVersionIfIdle = 0
     act_as_user users(:active) do
       # Create initial collection
       col = create_collection 'foo', Encoding::US_ASCII
     act_as_user users(:active) do
       # Create initial collection
       col = create_collection 'foo', Encoding::US_ASCII
@@ -489,13 +709,26 @@ class CollectionTest < ActiveSupport::TestCase
     end
   end
 
     end
   end
 
+  test "storage_classes_desired default respects config" do
+    saved = Rails.configuration.DefaultStorageClasses
+    Rails.configuration.DefaultStorageClasses = ["foo"]
+    begin
+      act_as_user users(:active) do
+        c = Collection.create!
+        assert_equal ["foo"], c.storage_classes_desired
+      end
+    ensure
+      Rails.configuration.DefaultStorageClasses = saved
+    end
+  end
+
   test "storage_classes_desired cannot be empty" do
     act_as_user users(:active) do
       c = collections(:collection_owned_by_active)
   test "storage_classes_desired cannot be empty" do
     act_as_user users(:active) do
       c = collections(:collection_owned_by_active)
-      c.update_attributes storage_classes_desired: ["hot"]
+      c.update storage_classes_desired: ["hot"]
       assert_equal ["hot"], c.storage_classes_desired
       assert_raise ArvadosModel::InvalidStateTransitionError do
       assert_equal ["hot"], c.storage_classes_desired
       assert_raise ArvadosModel::InvalidStateTransitionError do
-        c.update_attributes storage_classes_desired: []
+        c.update storage_classes_desired: []
       end
     end
   end
       end
     end
   end
@@ -503,7 +736,7 @@ class CollectionTest < ActiveSupport::TestCase
   test "storage classes lists should only contain non-empty strings" do
     c = collections(:storage_classes_desired_default_unconfirmed)
     act_as_user users(:admin) do
   test "storage classes lists should only contain non-empty strings" do
     c = collections(:storage_classes_desired_default_unconfirmed)
     act_as_user users(:admin) do
-      assert c.update_attributes(storage_classes_desired: ["default", "a_string"],
+      assert c.update(storage_classes_desired: ["default", "a_string"],
                                  storage_classes_confirmed: ["another_string"])
       [
         ["storage_classes_desired", ["default", 42]],
                                  storage_classes_confirmed: ["another_string"])
       [
         ["storage_classes_desired", ["default", 42]],
@@ -512,7 +745,7 @@ class CollectionTest < ActiveSupport::TestCase
         ["storage_classes_confirmed", [""]],
       ].each do |attr, val|
         assert_raise ArvadosModel::InvalidStateTransitionError do
         ["storage_classes_confirmed", [""]],
       ].each do |attr, val|
         assert_raise ArvadosModel::InvalidStateTransitionError do
-          assert c.update_attributes({attr => val})
+          assert c.update({attr => val})
         end
       end
     end
         end
       end
     end
@@ -521,7 +754,7 @@ class CollectionTest < ActiveSupport::TestCase
   test "storage_classes_confirmed* can be set by admin user" do
     c = collections(:storage_classes_desired_default_unconfirmed)
     act_as_user users(:admin) do
   test "storage_classes_confirmed* can be set by admin user" do
     c = collections(:storage_classes_desired_default_unconfirmed)
     act_as_user users(:admin) do
-      assert c.update_attributes(storage_classes_confirmed: ["default"],
+      assert c.update(storage_classes_confirmed: ["default"],
                                  storage_classes_confirmed_at: Time.now)
     end
   end
                                  storage_classes_confirmed_at: Time.now)
     end
   end
@@ -531,16 +764,16 @@ class CollectionTest < ActiveSupport::TestCase
       c = collections(:storage_classes_desired_default_unconfirmed)
       # Cannot set just one at a time.
       assert_raise ArvadosModel::PermissionDeniedError do
       c = collections(:storage_classes_desired_default_unconfirmed)
       # Cannot set just one at a time.
       assert_raise ArvadosModel::PermissionDeniedError do
-        c.update_attributes storage_classes_confirmed: ["default"]
+        c.update storage_classes_confirmed: ["default"]
       end
       c.reload
       assert_raise ArvadosModel::PermissionDeniedError do
       end
       c.reload
       assert_raise ArvadosModel::PermissionDeniedError do
-        c.update_attributes storage_classes_confirmed_at: Time.now
+        c.update storage_classes_confirmed_at: Time.now
       end
       # Cannot set bot at once, either.
       c.reload
       assert_raise ArvadosModel::PermissionDeniedError do
       end
       # Cannot set bot at once, either.
       c.reload
       assert_raise ArvadosModel::PermissionDeniedError do
-        assert c.update_attributes(storage_classes_confirmed: ["default"],
+        assert c.update(storage_classes_confirmed: ["default"],
                                    storage_classes_confirmed_at: Time.now)
       end
     end
                                    storage_classes_confirmed_at: Time.now)
       end
     end
@@ -551,25 +784,25 @@ class CollectionTest < ActiveSupport::TestCase
       c = collections(:storage_classes_desired_default_confirmed_default)
       # Cannot clear just one at a time.
       assert_raise ArvadosModel::PermissionDeniedError do
       c = collections(:storage_classes_desired_default_confirmed_default)
       # Cannot clear just one at a time.
       assert_raise ArvadosModel::PermissionDeniedError do
-        c.update_attributes storage_classes_confirmed: []
+        c.update storage_classes_confirmed: []
       end
       c.reload
       assert_raise ArvadosModel::PermissionDeniedError do
       end
       c.reload
       assert_raise ArvadosModel::PermissionDeniedError do
-        c.update_attributes storage_classes_confirmed_at: nil
+        c.update storage_classes_confirmed_at: nil
       end
       # Can clear both at once.
       c.reload
       end
       # Can clear both at once.
       c.reload
-      assert c.update_attributes(storage_classes_confirmed: [],
+      assert c.update(storage_classes_confirmed: [],
                                  storage_classes_confirmed_at: nil)
     end
   end
 
   [0, 2, 4, nil].each do |ask|
     test "set replication_desired to #{ask.inspect}" do
                                  storage_classes_confirmed_at: nil)
     end
   end
 
   [0, 2, 4, nil].each do |ask|
     test "set replication_desired to #{ask.inspect}" do
-      Rails.configuration.default_collection_replication = 2
+      Rails.configuration.Collections.DefaultReplication = 2
       act_as_user users(:active) do
         c = collections(:replication_undesired_unconfirmed)
       act_as_user users(:active) do
         c = collections(:replication_undesired_unconfirmed)
-        c.update_attributes replication_desired: ask
+        c.update replication_desired: ask
         assert_equal ask, c.replication_desired
       end
     end
         assert_equal ask, c.replication_desired
       end
     end
@@ -578,7 +811,7 @@ class CollectionTest < ActiveSupport::TestCase
   test "replication_confirmed* can be set by admin user" do
     c = collections(:replication_desired_2_unconfirmed)
     act_as_user users(:admin) do
   test "replication_confirmed* can be set by admin user" do
     c = collections(:replication_desired_2_unconfirmed)
     act_as_user users(:admin) do
-      assert c.update_attributes(replication_confirmed: 2,
+      assert c.update(replication_confirmed: 2,
                                  replication_confirmed_at: Time.now)
     end
   end
                                  replication_confirmed_at: Time.now)
     end
   end
@@ -588,14 +821,14 @@ class CollectionTest < ActiveSupport::TestCase
       c = collections(:replication_desired_2_unconfirmed)
       # Cannot set just one at a time.
       assert_raise ArvadosModel::PermissionDeniedError do
       c = collections(:replication_desired_2_unconfirmed)
       # Cannot set just one at a time.
       assert_raise ArvadosModel::PermissionDeniedError do
-        c.update_attributes replication_confirmed: 1
+        c.update replication_confirmed: 1
       end
       assert_raise ArvadosModel::PermissionDeniedError do
       end
       assert_raise ArvadosModel::PermissionDeniedError do
-        c.update_attributes replication_confirmed_at: Time.now
+        c.update replication_confirmed_at: Time.now
       end
       # Cannot set both at once, either.
       assert_raise ArvadosModel::PermissionDeniedError do
       end
       # Cannot set both at once, either.
       assert_raise ArvadosModel::PermissionDeniedError do
-        c.update_attributes(replication_confirmed: 1,
+        c.update(replication_confirmed: 1,
                             replication_confirmed_at: Time.now)
       end
     end
                             replication_confirmed_at: Time.now)
       end
     end
@@ -606,15 +839,15 @@ class CollectionTest < ActiveSupport::TestCase
       c = collections(:replication_desired_2_confirmed_2)
       # Cannot clear just one at a time.
       assert_raise ArvadosModel::PermissionDeniedError do
       c = collections(:replication_desired_2_confirmed_2)
       # Cannot clear just one at a time.
       assert_raise ArvadosModel::PermissionDeniedError do
-        c.update_attributes replication_confirmed: nil
+        c.update replication_confirmed: nil
       end
       c.reload
       assert_raise ArvadosModel::PermissionDeniedError do
       end
       c.reload
       assert_raise ArvadosModel::PermissionDeniedError do
-        c.update_attributes replication_confirmed_at: nil
+        c.update replication_confirmed_at: nil
       end
       # Can clear both at once.
       c.reload
       end
       # Can clear both at once.
       c.reload
-      assert c.update_attributes(replication_confirmed: nil,
+      assert c.update(replication_confirmed: nil,
                                  replication_confirmed_at: nil)
     end
   end
                                  replication_confirmed_at: nil)
     end
   end
@@ -622,7 +855,7 @@ class CollectionTest < ActiveSupport::TestCase
   test "clear replication_confirmed* when introducing a new block in manifest" do
     c = collections(:replication_desired_2_confirmed_2)
     act_as_user users(:active) do
   test "clear replication_confirmed* when introducing a new block in manifest" do
     c = collections(:replication_desired_2_confirmed_2)
     act_as_user users(:active) do
-      assert c.update_attributes(manifest_text: collections(:user_agreement).signed_manifest_text)
+      assert c.update(manifest_text: collections(:user_agreement).signed_manifest_text_only_for_tests)
       assert_nil c.replication_confirmed
       assert_nil c.replication_confirmed_at
     end
       assert_nil c.replication_confirmed
       assert_nil c.replication_confirmed_at
     end
@@ -631,8 +864,8 @@ class CollectionTest < ActiveSupport::TestCase
   test "don't clear replication_confirmed* when just renaming a file" do
     c = collections(:replication_desired_2_confirmed_2)
     act_as_user users(:active) do
   test "don't clear replication_confirmed* when just renaming a file" do
     c = collections(:replication_desired_2_confirmed_2)
     act_as_user users(:active) do
-      new_manifest = c.signed_manifest_text.sub(':bar', ':foo')
-      assert c.update_attributes(manifest_text: new_manifest)
+      new_manifest = c.signed_manifest_text_only_for_tests.sub(':bar', ':foo')
+      assert c.update(manifest_text: new_manifest)
       assert_equal 2, c.replication_confirmed
       assert_not_nil c.replication_confirmed_at
     end
       assert_equal 2, c.replication_confirmed
       assert_not_nil c.replication_confirmed_at
     end
@@ -641,15 +874,15 @@ class CollectionTest < ActiveSupport::TestCase
   test "don't clear replication_confirmed* when just deleting a data block" do
     c = collections(:replication_desired_2_confirmed_2)
     act_as_user users(:active) do
   test "don't clear replication_confirmed* when just deleting a data block" do
     c = collections(:replication_desired_2_confirmed_2)
     act_as_user users(:active) do
-      new_manifest = c.signed_manifest_text
+      new_manifest = c.signed_manifest_text_only_for_tests
       new_manifest.sub!(/ \S+:bar/, '')
       new_manifest.sub!(/ acbd\S+/, '')
 
       # Confirm that we did just remove a block from the manifest (if
       # not, this test would pass without testing the relevant case):
       new_manifest.sub!(/ \S+:bar/, '')
       new_manifest.sub!(/ acbd\S+/, '')
 
       # Confirm that we did just remove a block from the manifest (if
       # not, this test would pass without testing the relevant case):
-      assert_operator new_manifest.length+40, :<, c.signed_manifest_text.length
+      assert_operator new_manifest.length+40, :<, c.signed_manifest_text_only_for_tests.length
 
 
-      assert c.update_attributes(manifest_text: new_manifest)
+      assert c.update(manifest_text: new_manifest)
       assert_equal 2, c.replication_confirmed
       assert_not_nil c.replication_confirmed_at
     end
       assert_equal 2, c.replication_confirmed
       assert_not_nil c.replication_confirmed_at
     end
@@ -659,9 +892,9 @@ class CollectionTest < ActiveSupport::TestCase
     act_as_user users(:active) do
       t0 = db_current_time
       c = Collection.create!(manifest_text: ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:x\n", name: 'foo')
     act_as_user users(:active) do
       t0 = db_current_time
       c = Collection.create!(manifest_text: ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:x\n", name: 'foo')
-      c.update_attributes! trash_at: (t0 + 1.hours)
+      c.update! trash_at: (t0 + 1.hours)
       c.reload
       c.reload
-      sig_exp = /\+A[0-9a-f]{40}\@([0-9]+)/.match(c.signed_manifest_text)[1].to_i
+      sig_exp = /\+A[0-9a-f]{40}\@([0-9]+)/.match(c.signed_manifest_text_only_for_tests)[1].to_i
       assert_operator sig_exp.to_i, :<=, (t0 + 1.hours).to_i
     end
   end
       assert_operator sig_exp.to_i, :<=, (t0 + 1.hours).to_i
     end
   end
@@ -671,8 +904,8 @@ class CollectionTest < ActiveSupport::TestCase
       c = Collection.create!(manifest_text: ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:x\n",
                              name: 'foo',
                              trash_at: db_current_time + 1.years)
       c = Collection.create!(manifest_text: ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:x\n",
                              name: 'foo',
                              trash_at: db_current_time + 1.years)
-      sig_exp = /\+A[0-9a-f]{40}\@([0-9]+)/.match(c.signed_manifest_text)[1].to_i
-      expect_max_sig_exp = db_current_time.to_i + Rails.configuration.blob_signature_ttl
+      sig_exp = /\+A[0-9a-f]{40}\@([0-9]+)/.match(c.signed_manifest_text_only_for_tests)[1].to_i
+      expect_max_sig_exp = db_current_time.to_i + Rails.configuration.Collections.BlobSigningTTL.to_i
       assert_operator c.trash_at.to_i, :>, expect_max_sig_exp
       assert_operator sig_exp.to_i, :<=, expect_max_sig_exp
     end
       assert_operator c.trash_at.to_i, :>, expect_max_sig_exp
       assert_operator sig_exp.to_i, :<=, expect_max_sig_exp
     end
@@ -699,7 +932,7 @@ class CollectionTest < ActiveSupport::TestCase
       assert_not_empty c, 'Should be able to find live collection'
 
       # mark collection as expired
       assert_not_empty c, 'Should be able to find live collection'
 
       # mark collection as expired
-      c.first.update_attributes!(trash_at: Time.new.strftime("%Y-%m-%d"))
+      c.first.update!(trash_at: Time.new.strftime("%Y-%m-%d"))
       c = Collection.readable_by(current_user).where(uuid: uuid)
       assert_empty c, 'Should not be able to find expired collection'
 
       c = Collection.readable_by(current_user).where(uuid: uuid)
       assert_empty c, 'Should not be able to find expired collection'
 
@@ -714,7 +947,7 @@ class CollectionTest < ActiveSupport::TestCase
     act_as_user users(:active) do
       t0 = db_current_time
       c = Collection.create!(manifest_text: '', name: 'foo')
     act_as_user users(:active) do
       t0 = db_current_time
       c = Collection.create!(manifest_text: '', name: 'foo')
-      c.update_attributes! trash_at: (t0 - 2.weeks)
+      c.update! trash_at: (t0 - 2.weeks)
       c.reload
       assert_operator c.trash_at, :>, t0
     end
       c.reload
       assert_operator c.trash_at, :>, t0
     end
@@ -761,7 +994,7 @@ class CollectionTest < ActiveSupport::TestCase
     test test_name do
       act_as_user users(:active) do
         min_exp = (db_current_time +
     test test_name do
       act_as_user users(:active) do
         min_exp = (db_current_time +
-                   Rails.configuration.blob_signature_ttl.seconds)
+                   Rails.configuration.Collections.BlobSigningTTL)
         if fixture_name == :expired_collection
           # Fixture-finder shorthand doesn't find trashed collections
           # because they're not in the default scope.
         if fixture_name == :expired_collection
           # Fixture-finder shorthand doesn't find trashed collections
           # because they're not in the default scope.
@@ -769,7 +1002,7 @@ class CollectionTest < ActiveSupport::TestCase
         else
           c = collections(fixture_name)
         end
         else
           c = collections(fixture_name)
         end
-        updates_ok = c.update_attributes(updates)
+        updates_ok = c.update(updates)
         expect_valid = expect[:state] != :invalid
         assert_equal expect_valid, updates_ok, c.errors.full_messages.to_s
         case expect[:state]
         expect_valid = expect[:state] != :invalid
         assert_equal expect_valid, updates_ok, c.errors.full_messages.to_s
         case expect[:state]
@@ -802,17 +1035,17 @@ class CollectionTest < ActiveSupport::TestCase
   end
 
   test 'default trash interval > blob signature ttl' do
   end
 
   test 'default trash interval > blob signature ttl' do
-    Rails.configuration.default_trash_lifetime = 86400 * 21 # 3 weeks
+    Rails.configuration.Collections.DefaultTrashLifetime = 86400 * 21 # 3 weeks
     start = db_current_time
     act_as_user users(:active) do
       c = Collection.create!(manifest_text: '', name: 'foo')
     start = db_current_time
     act_as_user users(:active) do
       c = Collection.create!(manifest_text: '', name: 'foo')
-      c.update_attributes!(trash_at: start + 86400.seconds)
+      c.update!(trash_at: start + 86400.seconds)
       assert_operator c.delete_at, :>=, start + (86400*22).seconds
       assert_operator c.delete_at, :<, start + (86400*22 + 30).seconds
       c.destroy
 
       c = Collection.create!(manifest_text: '', name: 'foo')
       assert_operator c.delete_at, :>=, start + (86400*22).seconds
       assert_operator c.delete_at, :<, start + (86400*22 + 30).seconds
       c.destroy
 
       c = Collection.create!(manifest_text: '', name: 'foo')
-      c.update_attributes!(is_trashed: true)
+      c.update!(is_trashed: true)
       assert_operator c.delete_at, :>=, start + (86400*21).seconds
     end
   end
       assert_operator c.delete_at, :>=, start + (86400*21).seconds
     end
   end
@@ -824,44 +1057,102 @@ class CollectionTest < ActiveSupport::TestCase
     assert_includes(coll_uuids, collections(:docker_image).uuid)
   end
 
     assert_includes(coll_uuids, collections(:docker_image).uuid)
   end
 
-  test "move collections to trash in SweepTrashedObjects" do
-    c = collections(:trashed_on_next_sweep)
-    refute_empty Collection.where('uuid=? and is_trashed=false', c.uuid)
-    assert_raises(ActiveRecord::RecordNotUnique) do
-      act_as_user users(:active) do
-        Collection.create!(owner_uuid: c.owner_uuid,
-                           name: c.name)
+  test "empty names are exempt from name uniqueness" do
+    act_as_user users(:active) do
+      c1 = Collection.new(name: nil, manifest_text: '', owner_uuid: groups(:aproject).uuid)
+      assert c1.save
+      c2 = Collection.new(name: '', manifest_text: '', owner_uuid: groups(:aproject).uuid)
+      assert c2.save
+      c3 = Collection.new(name: '', manifest_text: '', owner_uuid: groups(:aproject).uuid)
+      assert c3.save
+      c4 = Collection.new(name: 'c4', manifest_text: '', owner_uuid: groups(:aproject).uuid)
+      assert c4.save
+      c5 = Collection.new(name: 'c4', manifest_text: '', owner_uuid: groups(:aproject).uuid)
+      assert_raises(ActiveRecord::RecordNotUnique) do
+        c5.save
       end
     end
       end
     end
-    SweepTrashedObjects.sweep_now
-    c = Collection.where('uuid=? and is_trashed=true', c.uuid).first
-    assert c
+  end
+
+  test "create collections with managed properties" do
+    Rails.configuration.Collections.ManagedProperties = ConfigLoader.to_OrderedOptions({
+      'default_prop1' => {'Value' => 'prop1_value'},
+      'responsible_person_uuid' => {'Function' => 'original_owner'}
+    })
+    # Test collection without initial properties
+    act_as_user users(:active) do
+      c = create_collection 'foo', Encoding::US_ASCII
+      assert c.valid?
+      assert_not_empty c.properties
+      assert_equal 'prop1_value', c.properties['default_prop1']
+      assert_equal users(:active).uuid, c.properties['responsible_person_uuid']
+    end
+    # Test collection with default_prop1 property already set
     act_as_user users(:active) do
     act_as_user users(:active) do
-      assert Collection.create!(owner_uuid: c.owner_uuid,
-                                name: c.name)
+      c = Collection.create(manifest_text: ". d41d8cd98f00b204e9800998ecf8427e 0:34:foo.txt\n",
+                            properties: {'default_prop1' => 'custom_value'})
+      assert c.valid?
+      assert_not_empty c.properties
+      assert_equal 'custom_value', c.properties['default_prop1']
+      assert_equal users(:active).uuid, c.properties['responsible_person_uuid']
+    end
+    # Test collection inside a sub project
+    act_as_user users(:active) do
+      c = Collection.create(manifest_text: ". d41d8cd98f00b204e9800998ecf8427e 0:34:foo.txt\n",
+                            owner_uuid: groups(:asubproject).uuid)
+      assert c.valid?
+      assert_not_empty c.properties
+      assert_equal users(:active).uuid, c.properties['responsible_person_uuid']
     end
   end
 
     end
   end
 
-  test "delete collections in SweepTrashedObjects" do
-    uuid = 'zzzzz-4zz18-3u1p5umicfpqszp' # deleted_on_next_sweep
-    assert_not_empty Collection.where(uuid: uuid)
-    SweepTrashedObjects.sweep_now
-    assert_empty Collection.where(uuid: uuid)
+  test "update collection with protected managed properties" do
+    Rails.configuration.Collections.ManagedProperties = ConfigLoader.to_OrderedOptions({
+      'default_prop1' => {'Value' => 'prop1_value', 'Protected' => true},
+    })
+    act_as_user users(:active) do
+      c = create_collection 'foo', Encoding::US_ASCII
+      assert c.valid?
+      assert_not_empty c.properties
+      assert_equal 'prop1_value', c.properties['default_prop1']
+      # Add new property
+      c.properties['prop2'] = 'value2'
+      c.save!
+      c.reload
+      assert_equal 'value2', c.properties['prop2']
+      # Try to change protected property's value
+      c.properties['default_prop1'] = 'new_value'
+      assert_raises(ArvadosModel::PermissionDeniedError) do
+        c.save!
+      end
+      # Admins are allowed to change protected properties
+      act_as_system_user do
+        c.properties['default_prop1'] = 'new_value'
+        c.save!
+        c.reload
+        assert_equal 'new_value', c.properties['default_prop1']
+      end
+    end
   end
 
   end
 
-  test "delete referring links in SweepTrashedObjects" do
-    uuid = collections(:trashed_on_next_sweep).uuid
-    act_as_system_user do
-      Link.create!(head_uuid: uuid,
-                   tail_uuid: system_user_uuid,
-                   link_class: 'whatever',
-                   name: 'something')
-    end
-    past = db_current_time
-    Collection.where(uuid: uuid).
-      update_all(is_trashed: true, trash_at: past, delete_at: past)
-    assert_not_empty Collection.where(uuid: uuid)
-    SweepTrashedObjects.sweep_now
-    assert_empty Collection.where(uuid: uuid)
+  test "collection names must be displayable in a filesystem" do
+    set_user_from_auth :active
+    ["", "{SOLIDUS}"].each do |subst|
+      Rails.configuration.Collections.ForwardSlashNameSubstitution = subst
+      c = Collection.create
+      [[nil, true],
+       ["", true],
+       [".", false],
+       ["..", false],
+       ["...", true],
+       ["..z..", true],
+       ["foo/bar", subst != ""],
+       ["../..", subst != ""],
+       ["/", subst != ""],
+      ].each do |name, valid|
+        c.name = name
+        assert_equal valid, c.valid?, "#{name.inspect} should be #{valid ? "valid" : "invalid"}"
+      end
+    end
   end
 end
   end
 end