Merge branch 'master' into 9998-unsigned_manifest
[arvados.git] / services / api / test / functional / arvados / v1 / collections_controller_test.rb
index 3257e494c500cb1b901cb65382f51b27afeba7ec..c285f8db20e825a74bff16022ba98f5058fc116c 100644 (file)
@@ -2,6 +2,8 @@ require 'test_helper'
 
 class Arvados::V1::CollectionsControllerTest < ActionController::TestCase
 
+  PERM_TOKEN_RE = /\+A[[:xdigit:]]+@[[:xdigit:]]{8}\b/
+
   def permit_unsigned_manifests isok=true
     # Set security model for the life of a test.
     Rails.configuration.permit_create_collection_with_unsigned_manifest = isok
@@ -10,11 +12,23 @@ class Arvados::V1::CollectionsControllerTest < ActionController::TestCase
   def assert_signed_manifest manifest_text, label=''
     assert_not_nil manifest_text, "#{label} manifest_text was nil"
     manifest_text.scan(/ [[:xdigit:]]{32}\S*/) do |tok|
-      assert_match(/\+A[[:xdigit:]]+@[[:xdigit:]]{8}\b/, tok,
+      assert_match(PERM_TOKEN_RE, tok,
                    "Locator in #{label} manifest_text was not signed")
     end
   end
 
+  def assert_unsigned_manifest resp, label=''
+    txt = resp['unsigned_manifest_text']
+    assert_not_nil(txt, "#{label} unsigned_manifest_text was nil")
+    locs = 0
+    txt.scan(/ [[:xdigit:]]{32}\S*/) do |tok|
+      locs += 1
+      refute_match(PERM_TOKEN_RE, tok,
+                   "Locator in #{label} unsigned_manifest_text was signed: #{tok}")
+    end
+    return locs
+  end
+
   test "should get index" do
     authorize_with :active
     get :index
@@ -24,12 +38,13 @@ class Arvados::V1::CollectionsControllerTest < ActionController::TestCase
            "basic Collections index included manifest_text")
   end
 
-  test "collections.get returns signed locators" do
+  test "collections.get returns signed locators, and no unsigned_manifest_text" do
     permit_unsigned_manifests
     authorize_with :active
     get :show, {id: collections(:foo_file).uuid}
     assert_response :success
     assert_signed_manifest json_response['manifest_text'], 'foo_file'
+    refute_includes json_response, 'unsigned_manifest_text'
   end
 
   test "index with manifest_text selected returns signed locators" do
@@ -40,12 +55,69 @@ class Arvados::V1::CollectionsControllerTest < ActionController::TestCase
     assert(assigns(:objects).andand.any?,
            "no Collections returned for index with columns selected")
     json_response["items"].each do |coll|
-      assert_equal(columns, columns & coll.keys,
+      assert_equal(coll.keys - ['kind'], columns,
                    "Collections index did not respect selected columns")
       assert_signed_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']
+    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']
+    end
+    assert_operator locs, :>, 0, "no locators found in any manifests"
+  end
+
+  test 'index without select returns everything except manifest' do
+    authorize_with :active
+    get :index
+    assert_response :success
+    assert json_response['items'].any?
+    json_response['items'].each do |coll|
+      assert_includes(coll.keys, 'uuid')
+      assert_includes(coll.keys, 'name')
+      assert_includes(coll.keys, 'created_at')
+      refute_includes(coll.keys, 'manifest_text')
+    end
+  end
+
+  ['', nil, false, 'null'].each do |select|
+    test "index with select=#{select.inspect} returns everything except manifest" do
+      authorize_with :active
+      get :index, select: select
+      assert_response :success
+      assert json_response['items'].any?
+      json_response['items'].each do |coll|
+        assert_includes(coll.keys, 'uuid')
+        assert_includes(coll.keys, 'name')
+        assert_includes(coll.keys, 'created_at')
+        refute_includes(coll.keys, 'manifest_text')
+      end
+    end
+  end
+
+  [["uuid"],
+   ["uuid", "manifest_text"],
+   '["uuid"]',
+   '["uuid", "manifest_text"]'].each do |select|
+    test "index with select=#{select.inspect} returns no name" do
+      authorize_with :active
+      get :index, select: select
+      assert_response :success
+      assert json_response['items'].any?
+      json_response['items'].each do |coll|
+        refute_includes(coll.keys, 'name')
+      end
+    end
+  end
+
   [0,1,2].each do |limit|
     test "get index with limit=#{limit}" do
       authorize_with :active
@@ -103,11 +175,36 @@ class Arvados::V1::CollectionsControllerTest < ActionController::TestCase
     }.merge(params)
   end
 
+  test "index with manifest_text limited by max_index_database_read returns non-empty" do
+    request_capped_index() { |_| 1 }
+    assert_response :success
+    assert_equal(1, json_response["items"].size)
+    assert_equal(1, json_response["limit"])
+    assert_equal(201, json_response["items_available"])
+  end
+
+  test "max_index_database_read size check follows same order as real query" do
+    authorize_with :user1_with_load
+    txt = '.' + ' d41d8cd98f00b204e9800998ecf8427e+0'*1000 + " 0:0:empty.txt\n"
+    c = Collection.create! manifest_text: txt, name: '0000000000000000000'
+    request_capped_index(select: %w(uuid manifest_text name),
+                         order: ['name asc'],
+                         filters: [['name','>=',c.name]]) do |_|
+      txt.length - 1
+    end
+    assert_response :success
+    assert_equal(1, json_response["items"].size)
+    assert_equal(1, json_response["limit"])
+    assert_equal(c.uuid, json_response["items"][0]["uuid"])
+    # The effectiveness of the test depends on >1 item matching the filters.
+    assert_operator(1, :<, json_response["items_available"])
+  end
+
   test "index with manifest_text limited by max_index_database_read" do
     request_capped_index() { |size| (size * 3) + 1 }
     assert_response :success
-    assert_equal(4, json_response["items"].size)
-    assert_equal(4, json_response["limit"])
+    assert_equal(3, json_response["items"].size)
+    assert_equal(3, json_response["limit"])
     assert_equal(201, json_response["items_available"])
   end
 
@@ -120,13 +217,14 @@ class Arvados::V1::CollectionsControllerTest < ActionController::TestCase
   end
 
   test "max_index_database_read does not interfere with order" do
-    request_capped_index(order: "name DESC") { |size| (size * 15) + 1 }
+    request_capped_index(select: %w(uuid manifest_text name),
+                         order: "name DESC") { |size| (size * 11) + 1 }
     assert_response :success
-    assert_equal(16, json_response["items"].size)
+    assert_equal(11, json_response["items"].size)
     assert_empty(json_response["items"].reject do |coll|
-                   coll["name"] !~ /^Collection_9/
+                   coll["name"] =~ /^Collection_9/
                  end)
-    assert_equal(16, json_response["limit"])
+    assert_equal(11, json_response["limit"])
     assert_equal(201, json_response["items_available"])
   end
 
@@ -505,8 +603,8 @@ EOS
     }
 
     # Generate a locator with a bad signature.
-    unsigned_locator = "d41d8cd98f00b204e9800998ecf8427e+0"
-    bad_locator = unsigned_locator + "+Affffffff@ffffffff"
+    unsigned_locator = "acbd18db4cc2f85cedef654fccc4a4d8+3"
+    bad_locator = unsigned_locator + "+Affffffffffffffffffffffffffffffffffffffff@ffffffff"
     assert !Blob.verify_signature(bad_locator, signing_opts)
 
     # Creating a collection with this locator should
@@ -551,6 +649,16 @@ EOS
     assert_response 422
   end
 
+  test "reject manifest with unsigned block as stream name" do
+    authorize_with :active
+    post :create, {
+      collection: {
+        manifest_text: "00000000000000000000000000000000+1234 d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo.txt\n"
+      }
+    }
+    assert_includes [422, 403], response.code.to_i
+  end
+
   test "multiple locators per line" do
     permit_unsigned_manifests
     authorize_with :active
@@ -773,4 +881,82 @@ EOS
     assert_not_nil json_response['uuid']
     assert_equal 'value_1', json_response['properties']['property_1']
   end
+
+  [
+    ". 0:0:foo.txt",
+    ". d41d8cd98f00b204e9800998ecf8427e foo.txt",
+    "d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt",
+    ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt",
+  ].each do |manifest_text|
+    test "create collection with invalid manifest #{manifest_text} and expect error" do
+      authorize_with :active
+      post :create, {
+        collection: {
+          manifest_text: manifest_text,
+          portable_data_hash: "d41d8cd98f00b204e9800998ecf8427e+0"
+        }
+      }
+      assert_response 422
+      response_errors = json_response['errors']
+      assert_not_nil response_errors, 'Expected error in response'
+      assert(response_errors.first.include?('Invalid manifest'),
+             "Expected 'Invalid manifest' error in #{response_errors.first}")
+    end
+  end
+
+  [
+    [nil, "d41d8cd98f00b204e9800998ecf8427e+0"],
+    ["", "d41d8cd98f00b204e9800998ecf8427e+0"],
+    [". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt\n", "d30fe8ae534397864cb96c544f4cf102+47"],
+  ].each do |manifest_text, pdh|
+    test "create collection with valid manifest #{manifest_text.inspect} and expect success" do
+      authorize_with :active
+      post :create, {
+        collection: {
+          manifest_text: manifest_text,
+          portable_data_hash: pdh
+        }
+      }
+      assert_response 200
+    end
+  end
+
+  [
+    ". 0:0:foo.txt",
+    ". d41d8cd98f00b204e9800998ecf8427e foo.txt",
+    "d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt",
+    ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt",
+  ].each do |manifest_text|
+    test "update collection with invalid manifest #{manifest_text} and expect error" do
+      authorize_with :active
+      post :update, {
+        id: 'zzzzz-4zz18-bv31uwvy3neko21',
+        collection: {
+          manifest_text: manifest_text,
+        }
+      }
+      assert_response 422
+      response_errors = json_response['errors']
+      assert_not_nil response_errors, 'Expected error in response'
+      assert(response_errors.first.include?('Invalid manifest'),
+             "Expected 'Invalid manifest' error in #{response_errors.first}")
+    end
+  end
+
+  [
+    nil,
+    "",
+    ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt\n",
+  ].each do |manifest_text|
+    test "update collection with valid manifest #{manifest_text.inspect} and expect success" do
+      authorize_with :active
+      post :update, {
+        id: 'zzzzz-4zz18-bv31uwvy3neko21',
+        collection: {
+          manifest_text: manifest_text,
+        }
+      }
+      assert_response 200
+    end
+  end
 end