X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/0f11eb78e075445d1769b445ee12328bdb397781..924f8f6c13c06afc8a83168929b249e0e8fa7d18:/services/api/test/functional/arvados/v1/collections_controller_test.rb diff --git a/services/api/test/functional/arvados/v1/collections_controller_test.rb b/services/api/test/functional/arvados/v1/collections_controller_test.rb index fdc54894fb..8a1d044d6a 100644 --- a/services/api/test/functional/arvados/v1/collections_controller_test.rb +++ b/services/api/test/functional/arvados/v1/collections_controller_test.rb @@ -11,7 +11,7 @@ class Arvados::V1::CollectionsControllerTest < ActionController::TestCase def permit_unsigned_manifests isok=true # Set security model for the life of a test. - Rails.configuration.permit_create_collection_with_unsigned_manifest = isok + Rails.configuration.Collections.BlobSigning = !isok end def assert_signed_manifest manifest_text, label='', token: false @@ -24,7 +24,7 @@ class Arvados::V1::CollectionsControllerTest < ActionController::TestCase exp = tok[/\+A[[:xdigit:]]+@([[:xdigit:]]+)/, 1].to_i(16) sig = Blob.sign_locator( bare, - key: Rails.configuration.blob_signing_key, + key: Rails.configuration.Collections.BlobSigningKey, expire: exp, api_token: token)[/\+A[^\+]*/, 0] assert_includes tok, sig @@ -32,8 +32,7 @@ class Arvados::V1::CollectionsControllerTest < ActionController::TestCase end end - def assert_unsigned_manifest resp, label='' - txt = resp['unsigned_manifest_text'] + def assert_unsigned_manifest txt, label='' assert_not_nil(txt, "#{label} unsigned_manifest_text was nil") locs = 0 txt.scan(/ [[:xdigit:]]{32}\S*/) do |tok| @@ -57,7 +56,7 @@ class Arvados::V1::CollectionsControllerTest < ActionController::TestCase test "get index with include_old_versions" do authorize_with :active - get :index, { + get :index, params: { include_old_versions: true } assert_response :success @@ -66,66 +65,59 @@ class Arvados::V1::CollectionsControllerTest < ActionController::TestCase "past version not included on index") end - test "collections.get returns signed locators, and no unsigned_manifest_text" do + test "collections.get returns unsigned locators, and no unsigned_manifest_text" do permit_unsigned_manifests authorize_with :active - get :show, {id: collections(:foo_file).uuid} + get :show, params: {id: collections(:foo_file).uuid} assert_response :success - assert_signed_manifest json_response['manifest_text'], 'foo_file' + assert_unsigned_manifest json_response["manifest_text"], 'foo_file' refute_includes json_response, 'unsigned_manifest_text' end ['v1token', 'v2token'].each do |token_method| - test "correct signatures are given for #{token_method}" do - token = api_client_authorizations(:active).send(token_method) - authorize_with_token token - get :show, {id: collections(:foo_file).uuid} - assert_response :success - assert_signed_manifest json_response['manifest_text'], 'foo_file', token: token - end - test "signatures with #{token_method} are accepted" do token = api_client_authorizations(:active).send(token_method) signed = Blob.sign_locator( 'acbd18db4cc2f85cedef654fccc4a4d8+3', - key: Rails.configuration.blob_signing_key, + key: Rails.configuration.Collections.BlobSigningKey, api_token: token) authorize_with_token token - put :update, { + put :update, params: { id: collections(:collection_owned_by_active).uuid, collection: { manifest_text: ". #{signed} 0:3:foo.txt\n", }, } assert_response :success - assert_signed_manifest json_response['manifest_text'], 'updated', token: token + assert_unsigned_manifest json_response['manifest_text'], 'updated' end end - test "index with manifest_text selected returns signed locators" do + test "index with manifest_text selected returns unsigned locators" do columns = %w(uuid owner_uuid manifest_text) authorize_with :active - get :index, select: columns + get :index, params: {select: columns} assert_response :success assert(assigns(:objects).andand.any?, "no Collections returned for index with columns selected") json_response["items"].each do |coll| assert_equal(coll.keys - ['kind'], columns, "Collections index did not respect selected columns") - assert_signed_manifest coll['manifest_text'], coll['uuid'] + assert_unsigned_manifest coll['manifest_text'], coll['uuid'] end end test "index with unsigned_manifest_text selected returns only unsigned locators" do authorize_with :active - get :index, select: ['unsigned_manifest_text'] + get :index, params: {select: ['unsigned_manifest_text']} assert_response :success assert_operator json_response["items"].count, :>, 0 locs = 0 json_response["items"].each do |coll| assert_equal(coll.keys - ['kind'], ['unsigned_manifest_text'], "Collections index did not respect selected columns") - locs += assert_unsigned_manifest coll, coll['uuid'] + assert_nil coll['manifest_text'] + locs += assert_unsigned_manifest coll['unsigned_manifest_text'], coll['uuid'] end assert_operator locs, :>, 0, "no locators found in any manifests" end @@ -146,7 +138,7 @@ class Arvados::V1::CollectionsControllerTest < ActionController::TestCase ['', nil, false, 'null'].each do |select| test "index with select=#{select.inspect} returns everything except manifest" do authorize_with :active - get :index, select: select + get :index, params: {select: select} assert_response :success assert json_response['items'].any? json_response['items'].each do |coll| @@ -164,7 +156,7 @@ class Arvados::V1::CollectionsControllerTest < ActionController::TestCase '["uuid", "manifest_text"]'].each do |select| test "index with select=#{select.inspect} returns no name" do authorize_with :active - get :index, select: select + get :index, params: {select: select} assert_response :success assert json_response['items'].any? json_response['items'].each do |coll| @@ -176,7 +168,7 @@ class Arvados::V1::CollectionsControllerTest < ActionController::TestCase [0,1,2].each do |limit| test "get index with limit=#{limit}" do authorize_with :active - get :index, limit: limit + get :index, params: {limit: limit} assert_response :success assert_equal limit, assigns(:objects).count resp = JSON.parse(@response.body) @@ -186,7 +178,7 @@ class Arvados::V1::CollectionsControllerTest < ActionController::TestCase test "items.count == items_available" do authorize_with :active - get :index, limit: 100000 + get :index, params: {limit: 100000} assert_response :success resp = JSON.parse(@response.body) assert_equal resp['items_available'], assigns(:objects).length @@ -197,7 +189,7 @@ class Arvados::V1::CollectionsControllerTest < ActionController::TestCase test "items.count == items_available with filters" do authorize_with :active - get :index, { + get :index, params: { limit: 100, filters: [['uuid','=',collections(:foo_file).uuid]] } @@ -210,7 +202,7 @@ class Arvados::V1::CollectionsControllerTest < ActionController::TestCase test "get index with limit=2 offset=99999" do # Assume there are not that many test fixtures. authorize_with :active - get :index, limit: 2, offset: 99999 + get :index, params: {limit: 2, offset: 99999} assert_response :success assert_equal 0, assigns(:objects).count resp = JSON.parse(@response.body) @@ -221,9 +213,9 @@ class Arvados::V1::CollectionsControllerTest < ActionController::TestCase def request_capped_index(params={}) authorize_with :user1_with_load coll1 = collections(:collection_1_of_201) - Rails.configuration.max_index_database_read = + Rails.configuration.API.MaxIndexDatabaseRead = yield(coll1.manifest_text.size) - get :index, { + get :index, params: { select: %w(uuid manifest_text), filters: [["owner_uuid", "=", coll1.owner_uuid]], limit: 300, @@ -301,7 +293,7 @@ EOS # post :create will modify test_collection in place, so we save a copy first. # Hash.deep_dup is not sufficient as it preserves references of strings (??!?) post_collection = Marshal.load(Marshal.dump(test_collection)) - post :create, { + post :create, params: { collection: post_collection } @@ -331,14 +323,14 @@ EOS foo_collection = collections(:foo_file) # Get foo_file using its portable data hash - get :show, { + get :show, params: { id: foo_collection[:portable_data_hash] } assert_response :success assert_not_nil assigns(:object) resp = assigns(:object) assert_equal foo_collection[:portable_data_hash], resp[:portable_data_hash] - assert_signed_manifest resp[:manifest_text] + assert_unsigned_manifest resp[:manifest_text] # The manifest in the response will have had permission hints added. # Remove any permission hints in the response before comparing it to the source. @@ -351,7 +343,7 @@ EOS permit_unsigned_manifests authorize_with :active manifest_text = ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt\n" - post :create, { + post :create, params: { collection: { owner_uuid: 'zzzzz-j7d0g-rew6elm53kancon', manifest_text: manifest_text, @@ -367,7 +359,7 @@ EOS permit_unsigned_manifests authorize_with :admin manifest_text = ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt\n" - post :create, { + post :create, params: { collection: { owner_uuid: 'zzzzz-tpzed-000000000000000', manifest_text: manifest_text, @@ -382,15 +374,33 @@ EOS "Expected 'duplicate key' error in #{response_errors.first}") end + [false, true].each do |ensure_unique_name| + test "create failure with duplicate name, ensure_unique_name #{ensure_unique_name}" do + authorize_with :active + post :create, params: { + collection: { + owner_uuid: users(:active).uuid, + manifest_text: "", + name: "this...............................................................................................................................................................................................................................................................name is too long" + }, + ensure_unique_name: ensure_unique_name + } + assert_response 422 + # check the real error isn't masked by an + # ensure_unique_name-related error (#19698) + assert_match /value too long for type/, json_response['errors'][0] + end + end + [false, true].each do |unsigned| test "create with duplicate name, ensure_unique_name, unsigned=#{unsigned}" do permit_unsigned_manifests unsigned authorize_with :active manifest_text = ". acbd18db4cc2f85cedef654fccc4a4d8+3 0:0:foo.txt\n" if !unsigned - manifest_text = Collection.sign_manifest manifest_text, api_token(:active) + manifest_text = Collection.sign_manifest_only_for_tests manifest_text, api_token(:active) end - post :create, { + post :create, params: { collection: { owner_uuid: users(:active).uuid, manifest_text: manifest_text, @@ -407,7 +417,7 @@ EOS permit_unsigned_manifests authorize_with :active manifest_text = ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt\n" - post :create, { + post :create, params: { collection: { owner_uuid: groups(:active_user_has_can_manage).uuid, manifest_text: manifest_text, @@ -423,7 +433,7 @@ EOS permit_unsigned_manifests authorize_with :active manifest_text = ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt\n" - post :create, { + post :create, params: { collection: { owner_uuid: groups(:all_users).uuid, manifest_text: manifest_text, @@ -437,7 +447,7 @@ EOS permit_unsigned_manifests authorize_with :active manifest_text = ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt\n" - post :create, { + post :create, params: { collection: { owner_uuid: groups(:public).uuid, manifest_text: manifest_text, @@ -451,7 +461,7 @@ EOS permit_unsigned_manifests authorize_with :admin manifest_text = ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt\n" - post :create, { + post :create, params: { collection: { owner_uuid: 'zzzzz-j7d0g-it30l961gq3t0oi', manifest_text: manifest_text, @@ -464,7 +474,7 @@ EOS test "should create with collection passed as json" do permit_unsigned_manifests authorize_with :active - post :create, { + post :create, params: { collection: <<-EOS { "manifest_text":". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt\n",\ @@ -478,7 +488,7 @@ EOS test "should fail to create with checksum mismatch" do permit_unsigned_manifests authorize_with :active - post :create, { + post :create, params: { collection: <<-EOS { "manifest_text":". d41d8cd98f00b204e9800998ecf8427e 0:0:bar.txt\n",\ @@ -492,7 +502,7 @@ EOS test "collection UUID is normalized when created" do permit_unsigned_manifests authorize_with :active - post :create, { + post :create, params: { collection: { manifest_text: ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt\n", portable_data_hash: "d30fe8ae534397864cb96c544f4cf102+47+Khint+Xhint+Zhint" @@ -506,7 +516,7 @@ EOS test "get full provenance for baz file" do authorize_with :active - get :provenance, id: 'ea10d51bcf88862dbcc36eb292017dfd+45' + get :provenance, params: {id: 'ea10d51bcf88862dbcc36eb292017dfd+45'} assert_response :success resp = JSON.parse(@response.body) assert_not_nil resp['ea10d51bcf88862dbcc36eb292017dfd+45'] # baz @@ -519,14 +529,14 @@ EOS test "get no provenance for foo file" do # spectator user cannot even see baz collection authorize_with :spectator - get :provenance, id: '1f4b0bc7583c2a7f9102c395f4ffc5e3+45' + get :provenance, params: {id: '1f4b0bc7583c2a7f9102c395f4ffc5e3+45'} assert_response 404 end test "get partial provenance for baz file" do # spectator user can see bar->baz job, but not foo->bar job authorize_with :spectator - get :provenance, id: 'ea10d51bcf88862dbcc36eb292017dfd+45' + get :provenance, params: {id: 'ea10d51bcf88862dbcc36eb292017dfd+45'} assert_response :success resp = JSON.parse(@response.body) assert_not_nil resp['ea10d51bcf88862dbcc36eb292017dfd+45'] # baz @@ -539,7 +549,7 @@ EOS test "search collections with 'any' operator" do expect_pdh = collections(:docker_image).portable_data_hash authorize_with :active - get :index, { + get :index, params: { where: { any: ['contains', expect_pdh[5..25]] } } assert_response :success @@ -566,7 +576,7 @@ EOS # Build a manifest with both signed and unsigned locators. signing_opts = { - key: Rails.configuration.blob_signing_key, + key: Rails.configuration.Collections.BlobSigningKey, api_token: api_token(:active), } signed_locators = locators.collect do |x| @@ -584,7 +594,7 @@ EOS ". " + signed_locators[1] + " 0:0:foo.txt\n" + ". " + signed_locators[2] + " 0:0:foo.txt\n" - post :create, { + post :create, params: { collection: { manifest_text: signed_manifest, portable_data_hash: manifest_uuid, @@ -594,10 +604,10 @@ EOS assert_not_nil assigns(:object) resp = JSON.parse(@response.body) assert_equal manifest_uuid, resp['portable_data_hash'] - # All of the locators in the output must be signed. + # All of the signatures in the output must be valid. resp['manifest_text'].lines.each do |entry| m = /([[:xdigit:]]{32}\+\S+)/.match(entry) - if m + if m && m[0].index('+A') assert Blob.verify_signature m[0], signing_opts end end @@ -622,7 +632,7 @@ EOS # TODO(twp): in phase 4, all locators will need to be signed, so # this test should break and will need to be rewritten. Issue #2755. signing_opts = { - key: Rails.configuration.blob_signing_key, + key: Rails.configuration.Collections.BlobSigningKey, api_token: api_token(:active), ttl: 3600 # 1 hour } @@ -631,7 +641,7 @@ EOS ". " + Blob.sign_locator(locators[1], signing_opts) + " 0:0:foo.txt\n" + ". " + Blob.sign_locator(locators[2], signing_opts) + " 0:0:foo.txt\n" - post :create, { + post :create, params: { collection: { manifest_text: signed_manifest, portable_data_hash: manifest_uuid, @@ -641,10 +651,10 @@ EOS assert_not_nil assigns(:object) resp = JSON.parse(@response.body) assert_equal manifest_uuid, resp['portable_data_hash'] - # All of the locators in the output must be signed. + # All of the signatures in the output must be valid. resp['manifest_text'].lines.each do |entry| m = /([[:xdigit:]]{32}\+\S+)/.match(entry) - if m + if m && m[0].index('+A') assert Blob.verify_signature m[0], signing_opts end end @@ -653,7 +663,7 @@ EOS test "create fails with invalid signature" do authorize_with :active signing_opts = { - key: Rails.configuration.blob_signing_key, + key: Rails.configuration.Collections.BlobSigningKey, api_token: api_token(:active), } @@ -670,7 +680,7 @@ EOS unsigned_manifest.length.to_s bad_manifest = ". #{bad_locator} 0:0:foo.txt\n" - post :create, { + post :create, params: { collection: { manifest_text: bad_manifest, portable_data_hash: manifest_uuid @@ -683,7 +693,7 @@ EOS test "create fails with uuid of signed manifest" do authorize_with :active signing_opts = { - key: Rails.configuration.blob_signing_key, + key: Rails.configuration.Collections.BlobSigningKey, api_token: api_token(:active), } @@ -694,7 +704,7 @@ EOS '+' + signed_manifest.length.to_s - post :create, { + post :create, params: { collection: { manifest_text: signed_manifest, portable_data_hash: manifest_uuid @@ -706,7 +716,7 @@ EOS test "reject manifest with unsigned block as stream name" do authorize_with :active - post :create, { + post :create, params: { collection: { manifest_text: "00000000000000000000000000000000+1234 d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo.txt\n" } @@ -732,7 +742,7 @@ EOS portable_data_hash: manifest_uuid, } post_collection = Marshal.load(Marshal.dump(test_collection)) - post :create, { + post :create, params: { collection: post_collection } assert_response :success @@ -746,56 +756,12 @@ EOS assert_equal manifest_text, stripped_manifest end - test "multiple signed locators per line" do - permit_unsigned_manifests - authorize_with :active - locators = %w( - d41d8cd98f00b204e9800998ecf8427e+0 - acbd18db4cc2f85cedef654fccc4a4d8+3 - ea10d51bcf88862dbcc36eb292017dfd+45) - - signing_opts = { - key: Rails.configuration.blob_signing_key, - api_token: api_token(:active), - } - - unsigned_manifest = [".", *locators, "0:0:foo.txt\n"].join(" ") - manifest_uuid = Digest::MD5.hexdigest(unsigned_manifest) + - '+' + - unsigned_manifest.length.to_s - - signed_locators = locators.map { |loc| Blob.sign_locator loc, signing_opts } - signed_manifest = [".", *signed_locators, "0:0:foo.txt\n"].join(" ") - - post :create, { - collection: { - manifest_text: signed_manifest, - portable_data_hash: manifest_uuid, - } - } - assert_response :success - assert_not_nil assigns(:object) - resp = JSON.parse(@response.body) - assert_equal manifest_uuid, resp['portable_data_hash'] - # All of the locators in the output must be signed. - # Each line is of the form "path locator locator ... 0:0:file.txt" - # entry.split[1..-2] will yield just the tokens in the middle of the line - returned_locator_count = 0 - resp['manifest_text'].lines.each do |entry| - entry.split[1..-2].each do |tok| - returned_locator_count += 1 - assert Blob.verify_signature tok, signing_opts - end - end - assert_equal locators.count, returned_locator_count - end - test 'Reject manifest with unsigned blob' do permit_unsigned_manifests false authorize_with :active unsigned_manifest = ". 0cc175b9c0f1b6a831c399e269772661+1 0:1:a.txt\n" manifest_uuid = Digest::MD5.hexdigest(unsigned_manifest) - post :create, { + post :create, params: { collection: { manifest_text: unsigned_manifest, portable_data_hash: manifest_uuid, @@ -809,7 +775,7 @@ EOS test 'List expired collection returns empty list' do authorize_with :active - get :index, { + get :index, params: { where: {name: 'expired_collection'}, } assert_response :success @@ -819,7 +785,7 @@ EOS test 'Show expired collection returns 404' do authorize_with :active - get :show, { + get :show, params: { id: 'zzzzz-4zz18-mto52zx1s7sn3ih', } assert_response 404 @@ -827,7 +793,7 @@ EOS test 'Update expired collection returns 404' do authorize_with :active - post :update, { + post :update, params: { id: 'zzzzz-4zz18-mto52zx1s7sn3ih', collection: { name: "still expired" @@ -838,7 +804,7 @@ EOS test 'List collection with future expiration time succeeds' do authorize_with :active - get :index, { + get :index, params: { where: {name: 'collection_expires_in_future'}, } found = assigns(:objects) @@ -848,7 +814,7 @@ EOS test 'Show collection with future expiration time succeeds' do authorize_with :active - get :show, { + get :show, params: { id: 'zzzzz-4zz18-padkqo7yb8d9i3j', } assert_response :success @@ -856,7 +822,7 @@ EOS test 'Update collection with future expiration time succeeds' do authorize_with :active - post :update, { + post :update, params: { id: 'zzzzz-4zz18-padkqo7yb8d9i3j', collection: { name: "still not expired" @@ -867,7 +833,7 @@ EOS test "get collection and verify that file_names is not included" do authorize_with :active - get :show, {id: collections(:foo_file).uuid} + get :show, params: {id: collections(:foo_file).uuid} assert_response :success assert_equal collections(:foo_file).uuid, json_response['uuid'] assert_nil json_response['file_names'] @@ -890,9 +856,11 @@ EOS description = description + description end - post :create, collection: { - manifest_text: ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo.txt\n", - description: description, + post :create, params: { + collection: { + manifest_text: ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo.txt\n", + description: description, + } } assert_response expected_response @@ -901,9 +869,9 @@ EOS [1, 5, nil].each do |ask| test "Set replication_desired=#{ask.inspect}" do - Rails.configuration.default_collection_replication = 2 + Rails.configuration.Collections.DefaultReplication = 2 authorize_with :active - put :update, { + put :update, params: { id: collections(:replication_undesired_unconfirmed).uuid, collection: { replication_desired: ask, @@ -916,25 +884,136 @@ EOS test "get collection with properties" do authorize_with :active - get :show, {id: collections(:collection_with_one_property).uuid} + get :show, params: {id: collections(:collection_with_one_property).uuid} assert_response :success assert_not_nil json_response['uuid'] assert_equal 'value1', json_response['properties']['property1'] end - test "create collection with properties" do + [ + {'property_1' => 'value_1'}, + "{\"property_1\":\"value_1\"}", + ].each do |p| + test "create collection with valid properties param #{p.inspect}" do + authorize_with :active + manifest_text = ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt\n" + post :create, params: { + collection: { + manifest_text: manifest_text, + portable_data_hash: "d30fe8ae534397864cb96c544f4cf102+47", + properties: p + } + } + assert_response :success + assert_not_nil json_response['uuid'] + assert_equal Hash, json_response['properties'].class, 'Collection properties attribute should be of type hash' + assert_equal 'value_1', json_response['properties']['property_1'] + end + end + + [ + false, + [], + 42, + 'some string', + '["json", "encoded", "array"]', + ].each do |p| + test "create collection with non-valid properties param #{p.inspect}" do + authorize_with :active + post :create, params: { + collection: { + name: "test collection with non-valid properties param '#{p.inspect}'", + manifest_text: '', + properties: p + } + } + assert_response 422 + response_errors = json_response['errors'] + assert_not_nil response_errors, 'Expected error in response' + 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 "create collection with valid manifest #{manifest} and expect file stats" do + authorize_with :active + post :create, params: { + collection: { + manifest_text: manifest + } + } + assert_response 200 + assert_equal count, json_response['file_count'] + assert_equal size, json_response['file_size_total'] + end + end + + test "update collection manifest and expect new file stats" do authorize_with :active - manifest_text = ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt\n" - post :create, { + post :update, params: { + id: collections(:collection_owned_by_active_with_file_stats).uuid, collection: { - manifest_text: manifest_text, - portable_data_hash: "d30fe8ae534397864cb96c544f4cf102+47", - properties: {'property_1' => 'value_1'} + manifest_text: ". d41d8cd98f00b204e9800998ecf8427e 0:34:foo.txt\n" } } - assert_response :success - assert_not_nil json_response['uuid'] - assert_equal 'value_1', json_response['properties']['property_1'] + assert_response 200 + assert_equal 1, json_response['file_count'] + assert_equal 34, json_response['file_size_total'] + end + + [ + ['file_count', 1], + ['file_size_total', 34] + ].each do |attribute, val| + test "create collection with #{attribute} and expect overwrite" do + authorize_with :active + post :create, params: { + collection: { + manifest_text: ". d41d8cd98f00b204e9800998ecf8427e 0:34:foo.txt\n", + "#{attribute}": 10 + } + } + assert_response 200 + assert_equal val, json_response[attribute] + end + end + + [ + ['file_count', 1], + ['file_size_total', 3] + ].each do |attribute, val| + test "update collection with #{attribute} and expect ignore" do + authorize_with :active + post :update, params: { + id: collections(:collection_owned_by_active_with_file_stats).uuid, + collection: { + "#{attribute}": 10 + } + } + assert_response 200 + assert_equal val, json_response[attribute] + end + end + + [ + ['file_count', 1], + ['file_size_total', 34] + ].each do |attribute, val| + test "update collection with #{attribute} and manifest and expect manifest values" do + authorize_with :active + post :update, params: { + id: collections(:collection_owned_by_active_with_file_stats).uuid, + collection: { + manifest_text: ". d41d8cd98f00b204e9800998ecf8427e 0:34:foo.txt\n", + "#{attribute}": 10 + } + } + assert_response 200 + assert_equal val, json_response[attribute] + end end [ @@ -945,7 +1024,7 @@ EOS ].each do |manifest_text| test "create collection with invalid manifest #{manifest_text} and expect error" do authorize_with :active - post :create, { + post :create, params: { collection: { manifest_text: manifest_text, portable_data_hash: "d41d8cd98f00b204e9800998ecf8427e+0" @@ -966,7 +1045,7 @@ EOS ].each do |manifest_text, pdh| test "create collection with valid manifest #{manifest_text.inspect} and expect success" do authorize_with :active - post :create, { + post :create, params: { collection: { manifest_text: manifest_text, portable_data_hash: pdh @@ -984,7 +1063,7 @@ EOS ].each do |manifest_text| test "update collection with invalid manifest #{manifest_text} and expect error" do authorize_with :active - post :update, { + post :update, params: { id: 'zzzzz-4zz18-bv31uwvy3neko21', collection: { manifest_text: manifest_text, @@ -1005,7 +1084,7 @@ EOS ].each do |manifest_text| test "update collection with valid manifest #{manifest_text.inspect} and expect success" do authorize_with :active - post :update, { + post :update, params: { id: 'zzzzz-4zz18-bv31uwvy3neko21', collection: { manifest_text: manifest_text, @@ -1015,20 +1094,74 @@ EOS end end - test 'get trashed collection with include_trash' do - uuid = 'zzzzz-4zz18-mto52zx1s7sn3ih' # expired_collection + [true, false].each do |include_trash| + test "get trashed collection with include_trash=#{include_trash}" do + uuid = 'zzzzz-4zz18-mto52zx1s7sn3ih' # expired_collection + authorize_with :active + get :show, params: { + id: uuid, + include_trash: include_trash, + } + if include_trash + assert_response 200 + else + assert_response 404 + end + end + end + + [:admin, :active].each do |user| + test "get trashed collection via filters and #{user} user without including its past versions" do + uuid = 'zzzzz-4zz18-mto52zx1s7sn3ih' # expired_collection + authorize_with user + get :index, params: { + filters: [["current_version_uuid", "=", uuid]], + include_trash: true, + } + assert_response 200 + # Only the current version is returned + assert_equal 1, json_response["items"].size + end + end + + [:admin, :active].each do |user| + test "get trashed collection via filters and #{user} user, including its past versions" do + uuid = 'zzzzz-4zz18-mto52zx1s7sn3ih' # expired_collection + authorize_with :admin + get :index, params: { + filters: [["current_version_uuid", "=", uuid]], + include_trash: true, + include_old_versions: true, + } + assert_response 200 + # Both current & past version are returned + assert_equal 2, json_response["items"].size + end + end + + test "trash collection also trash its past versions" do + uuid = collections(:collection_owned_by_active).uuid authorize_with :active - get :show, { + versions = Collection.where(current_version_uuid: uuid) + assert_equal 2, versions.size + versions.each do |col| + refute col.is_trashed + end + post :trash, params: { id: uuid, - include_trash: true, } assert_response 200 + versions = Collection.where(current_version_uuid: uuid) + assert_equal 2, versions.size + versions.each do |col| + assert col.is_trashed + end end test 'get trashed collection without include_trash' do uuid = 'zzzzz-4zz18-mto52zx1s7sn3ih' # expired_collection authorize_with :active - get :show, { + get :show, params: { id: uuid, } assert_response 404 @@ -1037,19 +1170,19 @@ EOS test 'trash collection using http DELETE verb' do uuid = collections(:collection_owned_by_active).uuid authorize_with :active - delete :destroy, { + delete :destroy, params: { id: uuid, } assert_response 200 c = Collection.find_by_uuid(uuid) assert_operator c.trash_at, :<, db_current_time - assert_equal c.delete_at, c.trash_at + Rails.configuration.blob_signature_ttl + assert_equal c.delete_at, c.trash_at + Rails.configuration.Collections.BlobSigningTTL end test 'delete long-trashed collection immediately using http DELETE verb' do uuid = 'zzzzz-4zz18-mto52zx1s7sn3ih' # expired_collection authorize_with :active - delete :destroy, { + delete :destroy, params: { id: uuid, } assert_response 200 @@ -1069,19 +1202,19 @@ EOS end authorize_with :active time_before_trashing = db_current_time - post :trash, { + post :trash, params: { id: uuid, } assert_response 200 c = Collection.find_by_uuid(uuid) assert_operator c.trash_at, :<, db_current_time - assert_operator c.delete_at, :>=, time_before_trashing + Rails.configuration.default_trash_lifetime + assert_operator c.delete_at, :>=, time_before_trashing + Rails.configuration.Collections.DefaultTrashLifetime end end test 'untrash a trashed collection' do authorize_with :active - post :untrash, { + post :untrash, params: { id: collections(:expired_collection).uuid, } assert_response 200 @@ -1091,7 +1224,7 @@ EOS test 'untrash error on not trashed collection' do authorize_with :active - post :untrash, { + post :untrash, params: { id: collections(:collection_owned_by_active).uuid, } assert_response 422 @@ -1100,7 +1233,7 @@ EOS [:active, :admin].each do |user| test "get trashed collections as #{user}" do authorize_with user - get :index, { + get :index, params: { filters: [["is_trashed", "=", true]], include_trash: true, } @@ -1122,7 +1255,7 @@ EOS test 'untrash collection with same name as another with no ensure unique name' do authorize_with :active - post :untrash, { + post :untrash, params: { id: collections(:trashed_collection_to_test_name_conflict_on_untrash).uuid, } assert_response 422 @@ -1130,7 +1263,7 @@ EOS test 'untrash collection with same name as another with ensure unique name' do authorize_with :active - post :untrash, { + post :untrash, params: { id: collections(:trashed_collection_to_test_name_conflict_on_untrash).uuid, ensure_unique_name: true } @@ -1143,7 +1276,7 @@ EOS test 'cannot show collection in trashed subproject' do authorize_with :active - get :show, { + get :show, params: { id: collections(:collection_in_trashed_subproject).uuid, format: :json } @@ -1153,7 +1286,7 @@ EOS test 'can show collection in untrashed subproject' do authorize_with :active Group.find_by_uuid(groups(:trashed_project).uuid).update! is_trashed: false - get :show, { + get :show, params: { id: collections(:collection_in_trashed_subproject).uuid, format: :json, } @@ -1162,7 +1295,7 @@ EOS test 'cannot index collection in trashed subproject' do authorize_with :active - get :index, { limit: 1000 } + get :index, params: { limit: 1000 } assert_response :success item_uuids = json_response['items'].map do |item| item['uuid'] @@ -1173,7 +1306,7 @@ EOS test 'can index collection in untrashed subproject' do authorize_with :active Group.find_by_uuid(groups(:trashed_project).uuid).update! is_trashed: false - get :index, { limit: 1000 } + get :index, params: { limit: 1000 } assert_response :success item_uuids = json_response['items'].map do |item| item['uuid'] @@ -1183,7 +1316,7 @@ EOS test 'can index trashed subproject collection with include_trash' do authorize_with :active - get :index, { + get :index, params: { include_trash: true, limit: 1000 } @@ -1196,8 +1329,8 @@ EOS test 'can get collection with past versions' do authorize_with :active - get :index, { - filters: [['uuid','=',collections(:collection_owned_by_active).uuid]], + get :index, params: { + filters: [['current_version_uuid','=',collections(:collection_owned_by_active).uuid]], include_old_versions: true } assert_response :success @@ -1213,7 +1346,7 @@ EOS test 'can get old version collection by uuid' do authorize_with :active - get :show, { + get :show, params: { id: collections(:collection_owned_by_active_past_version_1).uuid, } assert_response :success @@ -1221,17 +1354,26 @@ EOS json_response['name'] end + test 'can get old version collection by PDH' do + authorize_with :active + get :show, params: { + id: collections(:collection_owned_by_active_past_version_1).portable_data_hash, + } + assert_response :success + assert_equal collections(:collection_owned_by_active_past_version_1).portable_data_hash, + json_response['portable_data_hash'] + end + test 'version and current_version_uuid are ignored at creation time' do permit_unsigned_manifests authorize_with :active manifest_text = ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt\n" - post :create, { + post :create, params: { collection: { name: 'Test collection', version: 42, current_version_uuid: collections(:collection_owned_by_active).uuid, manifest_text: manifest_text, - # portable_data_hash: "d30fe8ae534397864cb96c544f4cf102+47" } } assert_response :success @@ -1239,4 +1381,154 @@ EOS assert_equal 1, resp['version'] assert_equal resp['uuid'], resp['current_version_uuid'] end + + test "update collection with versioning enabled" do + Rails.configuration.Collections.CollectionVersioning = true + Rails.configuration.Collections.PreserveVersionIfIdle = 1 # 1 second + + col = collections(:collection_owned_by_active) + assert_equal 2, col.version + assert col.modified_at < Time.now - 1.second + + token = api_client_authorizations(:active).v2token + signed = Blob.sign_locator( + 'acbd18db4cc2f85cedef654fccc4a4d8+3', + key: Rails.configuration.Collections.BlobSigningKey, + api_token: token) + authorize_with_token token + put :update, params: { + id: col.uuid, + collection: { + manifest_text: ". #{signed} 0:3:foo.txt\n", + }, + } + assert_response :success + assert_equal 3, json_response['version'] + end + + test "delete collection with versioning enabled" do + Rails.configuration.Collections.CollectionVersioning = true + Rails.configuration.Collections.PreserveVersionIfIdle = 1 # 1 second + + col = collections(:collection_owned_by_active) + assert_equal 2, col.version + assert col.modified_at < Time.now - 1.second + + authorize_with(:active) + post :trash, params: { + id: col.uuid, + } + assert_response :success + assert_equal col.version, json_response['version'], 'Trashing a collection should not create a new version' + end + + [['<', :<], + ['<=', :<=], + ['>', :>], + ['>=', :>=], + ['=', :==]].each do |op, rubyop| + test "filter collections by replication_desired #{op} replication_confirmed" do + authorize_with(:active) + get :index, params: { + filters: [["(replication_desired #{op} replication_confirmed)", "=", true]], + } + assert_response :success + json_response["items"].each do |c| + assert_operator(c["replication_desired"], rubyop, c["replication_confirmed"]) + end + end + end + + ["(replication_desired < bogus)", + "replication_desired < replication_confirmed", + "(replication_desired < replication_confirmed", + "(replication_desired ! replication_confirmed)", + "(replication_desired <)", + "(replication_desired < manifest_text)", + "(manifest_text < manifest_text)", # currently only numeric attrs are supported + "(replication_desired < 2)", # currently only attrs are supported, not literals + "(1 < 2)", + ].each do |expr| + test "invalid filter expression #{expr}" do + authorize_with(:active) + get :index, params: { + filters: [[expr, "=", true]], + } + assert_response 422 + end + end + + test "invalid op/arg with filter expression" do + authorize_with(:active) + get :index, params: { + filters: [["replication_desired < replication_confirmed", "!=", false]], + } + assert_response 422 + end + + ["storage_classes_desired", "storage_classes_confirmed"].each do |attr| + test "filter collections by #{attr}" do + authorize_with(:active) + get :index, params: { + filters: [[attr, "=", '["default"]']] + } + assert_response :success + assert_not_equal 0, json_response["items"].length + json_response["items"].each do |c| + assert_equal ["default"], c[attr] + end + end + end + + test "select param is respected in 'show' response" do + authorize_with :active + get :show, params: { + id: collections(:collection_owned_by_active).uuid, + select: ["name"], + } + assert_response :success + assert_raises ActiveModel::MissingAttributeError do + assigns(:object).manifest_text + end + assert_nil json_response["manifest_text"] + assert_nil json_response["properties"] + assert_equal collections(:collection_owned_by_active).name, json_response["name"] + end + + test "select param is respected in 'update' response" do + authorize_with :active + post :update, params: { + id: collections(:collection_owned_by_active).uuid, + collection: { + manifest_text: ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:foobar.txt\n", + }, + select: ["name"], + } + assert_response :success + assert_nil json_response["manifest_text"] + assert_nil json_response["properties"] + assert_equal collections(:collection_owned_by_active).name, json_response["name"] + end + + [nil, + [], + ["is_trashed", "trash_at"], + ["is_trashed", "trash_at", "portable_data_hash"], + ["portable_data_hash"], + ["portable_data_hash", "manifest_text"], + ].each do |select| + test "select=#{select.inspect} param is respected in 'get by pdh' response" do + authorize_with :active + get :show, params: { + id: collections(:collection_owned_by_active).portable_data_hash, + select: select, + } + assert_response :success + if !select || select.index("manifest_text") + assert_not_nil json_response["manifest_text"] + else + assert_nil json_response["manifest_text"] + end + end + end end