diff options
Diffstat (limited to 'activestorage/test/service')
-rw-r--r-- | activestorage/test/service/azure_storage_service_test.rb | 39 | ||||
-rw-r--r-- | activestorage/test/service/configurations.example.yml | 29 | ||||
-rw-r--r-- | activestorage/test/service/configurations.yml.enc | bin | 0 -> 2848 bytes | |||
-rw-r--r-- | activestorage/test/service/configurator_test.rb | 23 | ||||
-rw-r--r-- | activestorage/test/service/disk_service_test.rb | 18 | ||||
-rw-r--r-- | activestorage/test/service/gcs_service_test.rb | 55 | ||||
-rw-r--r-- | activestorage/test/service/mirror_service_test.rb | 66 | ||||
-rw-r--r-- | activestorage/test/service/s3_service_test.rb | 66 | ||||
-rw-r--r-- | activestorage/test/service/shared_service_tests.rb | 130 |
9 files changed, 426 insertions, 0 deletions
diff --git a/activestorage/test/service/azure_storage_service_test.rb b/activestorage/test/service/azure_storage_service_test.rb new file mode 100644 index 0000000000..09c2e7f99c --- /dev/null +++ b/activestorage/test/service/azure_storage_service_test.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require "service/shared_service_tests" +require "uri" + +if SERVICE_CONFIGURATIONS[:azure] + class ActiveStorage::Service::AzureStorageServiceTest < ActiveSupport::TestCase + SERVICE = ActiveStorage::Service.configure(:azure, SERVICE_CONFIGURATIONS) + + include ActiveStorage::Service::SharedServiceTests + + test "signed URL generation" do + url = @service.url(@key, expires_in: 5.minutes, + disposition: :inline, filename: ActiveStorage::Filename.new("avatar.png"), content_type: "image/png") + + assert_match(/(\S+)&rscd=inline%3B\+filename%3D%22avatar\.png%22%3B\+filename\*%3DUTF-8%27%27avatar\.png&rsct=image%2Fpng/, url) + assert_match SERVICE_CONFIGURATIONS[:azure][:container], url + end + + test "uploading a tempfile" do + begin + key = SecureRandom.base58(24) + data = "Something else entirely!" + + Tempfile.open do |file| + file.write(data) + file.rewind + @service.upload(key, file) + end + + assert_equal data, @service.download(key) + ensure + @service.delete(key) + end + end + end +else + puts "Skipping Azure Storage Service tests because no Azure configuration was supplied" +end diff --git a/activestorage/test/service/configurations.example.yml b/activestorage/test/service/configurations.example.yml new file mode 100644 index 0000000000..a63aa33302 --- /dev/null +++ b/activestorage/test/service/configurations.example.yml @@ -0,0 +1,29 @@ +# s3: +# service: S3 +# access_key_id: "" +# secret_access_key: "" +# region: "" +# bucket: "" +# +# gcs: +# service: GCS +# credentials: { +# type: "service_account", +# project_id: "", +# private_key_id: "", +# private_key: "", +# client_email: "", +# client_id: "", +# auth_uri: "https://accounts.google.com/o/oauth2/auth", +# token_uri: "https://accounts.google.com/o/oauth2/token", +# auth_provider_x509_cert_url: "https://www.googleapis.com/oauth2/v1/certs", +# client_x509_cert_url: "" +# } +# project: +# bucket: +# +# azure: +# service: AzureStorage +# storage_account_name: "" +# storage_access_key: "" +# container: "" diff --git a/activestorage/test/service/configurations.yml.enc b/activestorage/test/service/configurations.yml.enc Binary files differnew file mode 100644 index 0000000000..648924a562 --- /dev/null +++ b/activestorage/test/service/configurations.yml.enc diff --git a/activestorage/test/service/configurator_test.rb b/activestorage/test/service/configurator_test.rb new file mode 100644 index 0000000000..3ef9cf9fb6 --- /dev/null +++ b/activestorage/test/service/configurator_test.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require "service/shared_service_tests" + +class ActiveStorage::Service::ConfiguratorTest < ActiveSupport::TestCase + test "builds correct service instance based on service name" do + service = ActiveStorage::Service::Configurator.build(:foo, foo: { service: "Disk", root: "path" }) + assert_instance_of ActiveStorage::Service::DiskService, service + assert_equal "path", service.root + end + + test "builds correct service instance based on lowercase service name" do + service = ActiveStorage::Service::Configurator.build(:foo, foo: { service: "disk", root: "path" }) + assert_instance_of ActiveStorage::Service::DiskService, service + assert_equal "path", service.root + end + + test "raises error when passing non-existent service name" do + assert_raise RuntimeError do + ActiveStorage::Service::Configurator.build(:bigfoot, {}) + end + end +end diff --git a/activestorage/test/service/disk_service_test.rb b/activestorage/test/service/disk_service_test.rb new file mode 100644 index 0000000000..a0218bff1c --- /dev/null +++ b/activestorage/test/service/disk_service_test.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require "service/shared_service_tests" + +class ActiveStorage::Service::DiskServiceTest < ActiveSupport::TestCase + SERVICE = ActiveStorage::Service::DiskService.new(root: File.join(Dir.tmpdir, "active_storage")) + + include ActiveStorage::Service::SharedServiceTests + + test "url generation" do + assert_match(/^https:\/\/example.com\/rails\/active_storage\/disk\/.*\/avatar\.png\?content_type=image%2Fpng&disposition=inline/, + @service.url(@key, expires_in: 5.minutes, disposition: :inline, filename: ActiveStorage::Filename.new("avatar.png"), content_type: "image/png")) + end + + test "headers_for_direct_upload generation" do + assert_equal({ "Content-Type" => "application/json" }, @service.headers_for_direct_upload(@key, content_type: "application/json")) + end +end diff --git a/activestorage/test/service/gcs_service_test.rb b/activestorage/test/service/gcs_service_test.rb new file mode 100644 index 0000000000..2ba2f8b346 --- /dev/null +++ b/activestorage/test/service/gcs_service_test.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require "service/shared_service_tests" +require "net/http" + +if SERVICE_CONFIGURATIONS[:gcs] + class ActiveStorage::Service::GCSServiceTest < ActiveSupport::TestCase + SERVICE = ActiveStorage::Service.configure(:gcs, SERVICE_CONFIGURATIONS) + + include ActiveStorage::Service::SharedServiceTests + + test "direct upload" do + begin + key = SecureRandom.base58(24) + data = "Something else entirely!" + checksum = Digest::MD5.base64digest(data) + url = @service.url_for_direct_upload(key, expires_in: 5.minutes, content_type: "text/plain", content_length: data.size, checksum: checksum) + + uri = URI.parse url + request = Net::HTTP::Put.new uri.request_uri + request.body = data + request.add_field "Content-Type", "" + request.add_field "Content-MD5", checksum + Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http| + http.request request + end + + assert_equal data, @service.download(key) + ensure + @service.delete key + end + end + + test "signed URL generation" do + assert_match(/storage\.googleapis\.com\/.*response-content-disposition=inline.*test\.txt.*response-content-type=text%2Fplain/, + @service.url(@key, expires_in: 2.minutes, disposition: :inline, filename: ActiveStorage::Filename.new("test.txt"), content_type: "text/plain")) + end + + test "signed URL response headers" do + begin + key = SecureRandom.base58(24) + data = "Something else entirely!" + @service.upload(key, StringIO.new(data), checksum: Digest::MD5.base64digest(data)) + + url = @service.url(key, expires_in: 2.minutes, disposition: :inline, filename: ActiveStorage::Filename.new("test.txt"), content_type: "text/plain") + response = Net::HTTP.get_response(URI(url)) + assert_equal "text/plain", response.content_type + ensure + @service.delete key + end + end + end +else + puts "Skipping GCS Service tests because no GCS configuration was supplied" +end diff --git a/activestorage/test/service/mirror_service_test.rb b/activestorage/test/service/mirror_service_test.rb new file mode 100644 index 0000000000..bb502dde60 --- /dev/null +++ b/activestorage/test/service/mirror_service_test.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +require "service/shared_service_tests" + +class ActiveStorage::Service::MirrorServiceTest < ActiveSupport::TestCase + mirror_config = (1..3).map do |i| + [ "mirror_#{i}", + service: "Disk", + root: Dir.mktmpdir("active_storage_tests_mirror_#{i}") ] + end.to_h + + config = mirror_config.merge \ + mirror: { service: "Mirror", primary: "primary", mirrors: mirror_config.keys }, + primary: { service: "Disk", root: Dir.mktmpdir("active_storage_tests_primary") } + + SERVICE = ActiveStorage::Service.configure :mirror, config + + include ActiveStorage::Service::SharedServiceTests + + test "uploading to all services" do + begin + key = SecureRandom.base58(24) + data = "Something else entirely!" + io = StringIO.new(data) + checksum = Digest::MD5.base64digest(data) + + @service.upload key, io.tap(&:read), checksum: checksum + assert_predicate io, :eof? + + assert_equal data, @service.primary.download(key) + @service.mirrors.each do |mirror| + assert_equal data, mirror.download(key) + end + ensure + @service.delete key + end + end + + test "downloading from primary service" do + key = SecureRandom.base58(24) + data = "Something else entirely!" + checksum = Digest::MD5.base64digest(data) + + @service.primary.upload key, StringIO.new(data), checksum: checksum + + assert_equal data, @service.download(key) + end + + test "deleting from all services" do + @service.delete @key + + assert_not SERVICE.primary.exist?(@key) + SERVICE.mirrors.each do |mirror| + assert_not mirror.exist?(@key) + end + end + + test "URL generation in primary service" do + filename = ActiveStorage::Filename.new("test.txt") + + freeze_time do + assert_equal @service.primary.url(@key, expires_in: 2.minutes, disposition: :inline, filename: filename, content_type: "text/plain"), + @service.url(@key, expires_in: 2.minutes, disposition: :inline, filename: filename, content_type: "text/plain") + end + end +end diff --git a/activestorage/test/service/s3_service_test.rb b/activestorage/test/service/s3_service_test.rb new file mode 100644 index 0000000000..559aa028f2 --- /dev/null +++ b/activestorage/test/service/s3_service_test.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +require "service/shared_service_tests" +require "net/http" + +if SERVICE_CONFIGURATIONS[:s3] + class ActiveStorage::Service::S3ServiceTest < ActiveSupport::TestCase + SERVICE = ActiveStorage::Service.configure(:s3, SERVICE_CONFIGURATIONS) + + include ActiveStorage::Service::SharedServiceTests + + test "direct upload" do + begin + key = SecureRandom.base58(24) + data = "Something else entirely!" + checksum = Digest::MD5.base64digest(data) + url = @service.url_for_direct_upload(key, expires_in: 5.minutes, content_type: "text/plain", content_length: data.size, checksum: checksum) + + uri = URI.parse url + request = Net::HTTP::Put.new uri.request_uri + request.body = data + request.add_field "Content-Type", "text/plain" + request.add_field "Content-MD5", checksum + Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http| + http.request request + end + + assert_equal data, @service.download(key) + ensure + @service.delete key + end + end + + test "upload a zero byte file" do + blob = directly_upload_file_blob filename: "empty_file.txt", content_type: nil + user = User.create! name: "DHH", avatar: blob + + assert_equal user.avatar.blob, blob + end + + test "signed URL generation" do + url = @service.url(@key, expires_in: 5.minutes, + disposition: :inline, filename: ActiveStorage::Filename.new("avatar.png"), content_type: "image/png") + + assert_match(/s3(-[-a-z0-9]+)?\.(\S+)?amazonaws.com.*response-content-disposition=inline.*avatar\.png.*response-content-type=image%2Fpng/, url) + assert_match SERVICE_CONFIGURATIONS[:s3][:bucket], url + end + + test "uploading with server-side encryption" do + config = SERVICE_CONFIGURATIONS.deep_merge(s3: { upload: { server_side_encryption: "AES256" } }) + service = ActiveStorage::Service.configure(:s3, config) + + begin + key = SecureRandom.base58(24) + data = "Something else entirely!" + service.upload key, StringIO.new(data), checksum: Digest::MD5.base64digest(data) + + assert_equal "AES256", service.bucket.object(key).server_side_encryption + ensure + service.delete key + end + end + end +else + puts "Skipping S3 Service tests because no S3 configuration was supplied" +end diff --git a/activestorage/test/service/shared_service_tests.rb b/activestorage/test/service/shared_service_tests.rb new file mode 100644 index 0000000000..58f189af2b --- /dev/null +++ b/activestorage/test/service/shared_service_tests.rb @@ -0,0 +1,130 @@ +# frozen_string_literal: true + +require "test_helper" +require "active_support/core_ext/securerandom" + +module ActiveStorage::Service::SharedServiceTests + extend ActiveSupport::Concern + + FIXTURE_DATA = "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\000\020\000\000\000\020\001\003\000\000\000%=m\"\000\000\000\006PLTE\000\000\000\377\377\377\245\331\237\335\000\000\0003IDATx\234c\370\377\237\341\377_\206\377\237\031\016\2603\334?\314p\1772\303\315\315\f7\215\031\356\024\203\320\275\317\f\367\201R\314\f\017\300\350\377\177\000Q\206\027(\316]\233P\000\000\000\000IEND\256B`\202".dup.force_encoding(Encoding::BINARY) + + included do + setup do + @key = SecureRandom.base58(24) + @service = self.class.const_get(:SERVICE) + @service.upload @key, StringIO.new(FIXTURE_DATA) + end + + teardown do + @service.delete @key + end + + test "uploading with integrity" do + begin + key = SecureRandom.base58(24) + data = "Something else entirely!" + @service.upload(key, StringIO.new(data), checksum: Digest::MD5.base64digest(data)) + + assert_equal data, @service.download(key) + ensure + @service.delete key + end + end + + test "uploading without integrity" do + begin + key = SecureRandom.base58(24) + data = "Something else entirely!" + + assert_raises(ActiveStorage::IntegrityError) do + @service.upload(key, StringIO.new(data), checksum: Digest::MD5.base64digest("bad data")) + end + + assert_not @service.exist?(key) + ensure + @service.delete key + end + end + + test "downloading" do + assert_equal FIXTURE_DATA, @service.download(@key) + end + + test "downloading a nonexistent file" do + assert_raises(ActiveStorage::FileNotFoundError) do + @service.download(SecureRandom.base58(24)) + end + end + + + test "downloading in chunks" do + key = SecureRandom.base58(24) + expected_chunks = [ "a" * 5.megabytes, "b" ] + actual_chunks = [] + + begin + @service.upload key, StringIO.new(expected_chunks.join) + + @service.download key do |chunk| + actual_chunks << chunk + end + + assert_equal expected_chunks, actual_chunks, "Downloaded chunks did not match uploaded data" + ensure + @service.delete key + end + end + + test "downloading a nonexistent file in chunks" do + assert_raises(ActiveStorage::FileNotFoundError) do + @service.download(SecureRandom.base58(24)) {} + end + end + + + test "downloading partially" do + assert_equal "\x10\x00\x00", @service.download_chunk(@key, 19..21) + assert_equal "\x10\x00\x00", @service.download_chunk(@key, 19...22) + end + + test "partially downloading a nonexistent file" do + assert_raises(ActiveStorage::FileNotFoundError) do + @service.download_chunk(SecureRandom.base58(24), 19..21) + end + end + + + test "existing" do + assert @service.exist?(@key) + assert_not @service.exist?(@key + "nonsense") + end + + test "deleting" do + @service.delete @key + assert_not @service.exist?(@key) + end + + test "deleting nonexistent key" do + assert_nothing_raised do + @service.delete SecureRandom.base58(24) + end + end + + test "deleting by prefix" do + begin + @service.upload("a/a/a", StringIO.new(FIXTURE_DATA)) + @service.upload("a/a/b", StringIO.new(FIXTURE_DATA)) + @service.upload("a/b/a", StringIO.new(FIXTURE_DATA)) + + @service.delete_prefixed("a/a/") + assert_not @service.exist?("a/a/a") + assert_not @service.exist?("a/a/b") + assert @service.exist?("a/b/a") + ensure + @service.delete("a/a/a") + @service.delete("a/a/b") + @service.delete("a/b/a") + end + end + end +end |