diff options
Diffstat (limited to 'activestorage/test')
82 files changed, 2226 insertions, 0 deletions
diff --git a/activestorage/test/analyzer/image_analyzer_test.rb b/activestorage/test/analyzer/image_analyzer_test.rb new file mode 100644 index 0000000000..0d9f24c5c1 --- /dev/null +++ b/activestorage/test/analyzer/image_analyzer_test.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require "test_helper" +require "database/setup" + +require "active_storage/analyzer/image_analyzer" + +class ActiveStorage::Analyzer::ImageAnalyzerTest < ActiveSupport::TestCase + test "analyzing a JPEG image" do + blob = create_file_blob(filename: "racecar.jpg", content_type: "image/jpeg") + metadata = blob.tap(&:analyze).metadata + + assert_equal 4104, metadata[:width] + assert_equal 2736, metadata[:height] + end + + test "analyzing an SVG image without an XML declaration" do + blob = create_file_blob(filename: "icon.svg", content_type: "image/svg+xml") + metadata = blob.tap(&:analyze).metadata + + assert_equal 792, metadata[:width] + assert_equal 584, metadata[:height] + end +end diff --git a/activestorage/test/analyzer/video_analyzer_test.rb b/activestorage/test/analyzer/video_analyzer_test.rb new file mode 100644 index 0000000000..b3b9c97fe4 --- /dev/null +++ b/activestorage/test/analyzer/video_analyzer_test.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require "test_helper" +require "database/setup" + +require "active_storage/analyzer/video_analyzer" + +class ActiveStorage::Analyzer::VideoAnalyzerTest < ActiveSupport::TestCase + test "analyzing a video" do + blob = create_file_blob(filename: "video.mp4", content_type: "video/mp4") + metadata = blob.tap(&:analyze).metadata + + assert_equal 640, metadata[:width] + assert_equal 480, metadata[:height] + assert_equal [4, 3], metadata[:aspect_ratio] + assert_equal 5.166648, metadata[:duration] + assert_not_includes metadata, :angle + end + + test "analyzing a rotated video" do + blob = create_file_blob(filename: "rotated_video.mp4", content_type: "video/mp4") + metadata = blob.tap(&:analyze).metadata + + assert_equal 480, metadata[:width] + assert_equal 640, metadata[:height] + assert_equal [4, 3], metadata[:aspect_ratio] + assert_equal 5.227975, metadata[:duration] + assert_equal 90, metadata[:angle] + end + + test "analyzing a video without a video stream" do + blob = create_file_blob(filename: "video_without_video_stream.mp4", content_type: "video/mp4") + assert_equal({ "analyzed" => true }, blob.tap(&:analyze).metadata) + end +end diff --git a/activestorage/test/controllers/blobs_controller_test.rb b/activestorage/test/controllers/blobs_controller_test.rb new file mode 100644 index 0000000000..9c811df895 --- /dev/null +++ b/activestorage/test/controllers/blobs_controller_test.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require "test_helper" +require "database/setup" + +class ActiveStorage::BlobsControllerTest < ActionDispatch::IntegrationTest + setup do + @blob = create_file_blob filename: "racecar.jpg" + end + + test "showing blob with invalid signed ID" do + get rails_service_blob_url("invalid", "racecar.jpg") + assert_response :not_found + end + + test "showing blob utilizes browser caching" do + get rails_blob_url(@blob) + + assert_redirected_to(/racecar\.jpg/) + assert_equal "max-age=300, private", @response.headers["Cache-Control"] + end +end diff --git a/activestorage/test/controllers/direct_uploads_controller_test.rb b/activestorage/test/controllers/direct_uploads_controller_test.rb new file mode 100644 index 0000000000..888767086c --- /dev/null +++ b/activestorage/test/controllers/direct_uploads_controller_test.rb @@ -0,0 +1,124 @@ +# frozen_string_literal: true + +require "test_helper" +require "database/setup" + +if SERVICE_CONFIGURATIONS[:s3] && SERVICE_CONFIGURATIONS[:s3][:access_key_id].present? + class ActiveStorage::S3DirectUploadsControllerTest < ActionDispatch::IntegrationTest + setup do + @old_service = ActiveStorage::Blob.service + ActiveStorage::Blob.service = ActiveStorage::Service.configure(:s3, SERVICE_CONFIGURATIONS) + end + + teardown do + ActiveStorage::Blob.service = @old_service + end + + test "creating new direct upload" do + checksum = Digest::MD5.base64digest("Hello") + + post rails_direct_uploads_url, params: { blob: { + filename: "hello.txt", byte_size: 6, checksum: checksum, content_type: "text/plain" } } + + response.parsed_body.tap do |details| + assert_equal ActiveStorage::Blob.find(details["id"]), ActiveStorage::Blob.find_signed(details["signed_id"]) + assert_equal "hello.txt", details["filename"] + assert_equal 6, details["byte_size"] + assert_equal checksum, details["checksum"] + assert_equal "text/plain", details["content_type"] + assert_match SERVICE_CONFIGURATIONS[:s3][:bucket], details["direct_upload"]["url"] + assert_match(/s3\.(\S+)?amazonaws\.com/, details["direct_upload"]["url"]) + assert_equal({ "Content-Type" => "text/plain", "Content-MD5" => checksum }, details["direct_upload"]["headers"]) + end + end + end +else + puts "Skipping S3 Direct Upload tests because no S3 configuration was supplied" +end + +if SERVICE_CONFIGURATIONS[:gcs] + class ActiveStorage::GCSDirectUploadsControllerTest < ActionDispatch::IntegrationTest + setup do + @config = SERVICE_CONFIGURATIONS[:gcs] + + @old_service = ActiveStorage::Blob.service + ActiveStorage::Blob.service = ActiveStorage::Service.configure(:gcs, SERVICE_CONFIGURATIONS) + end + + teardown do + ActiveStorage::Blob.service = @old_service + end + + test "creating new direct upload" do + checksum = Digest::MD5.base64digest("Hello") + + post rails_direct_uploads_url, params: { blob: { + filename: "hello.txt", byte_size: 6, checksum: checksum, content_type: "text/plain" } } + + @response.parsed_body.tap do |details| + assert_equal ActiveStorage::Blob.find(details["id"]), ActiveStorage::Blob.find_signed(details["signed_id"]) + assert_equal "hello.txt", details["filename"] + assert_equal 6, details["byte_size"] + assert_equal checksum, details["checksum"] + assert_equal "text/plain", details["content_type"] + assert_match %r{storage\.googleapis\.com/#{@config[:bucket]}}, details["direct_upload"]["url"] + assert_equal({ "Content-Type" => "text/plain", "Content-MD5" => checksum }, details["direct_upload"]["headers"]) + end + end + end +else + puts "Skipping GCS Direct Upload tests because no GCS configuration was supplied" +end + +if SERVICE_CONFIGURATIONS[:azure] + class ActiveStorage::AzureStorageDirectUploadsControllerTest < ActionDispatch::IntegrationTest + setup do + @config = SERVICE_CONFIGURATIONS[:azure] + + @old_service = ActiveStorage::Blob.service + ActiveStorage::Blob.service = ActiveStorage::Service.configure(:azure, SERVICE_CONFIGURATIONS) + end + + teardown do + ActiveStorage::Blob.service = @old_service + end + + test "creating new direct upload" do + checksum = Digest::MD5.base64digest("Hello") + + post rails_direct_uploads_url, params: { blob: { + filename: "hello.txt", byte_size: 6, checksum: checksum, content_type: "text/plain" } } + + @response.parsed_body.tap do |details| + assert_equal ActiveStorage::Blob.find(details["id"]), ActiveStorage::Blob.find_signed(details["signed_id"]) + assert_equal "hello.txt", details["filename"] + assert_equal 6, details["byte_size"] + assert_equal checksum, details["checksum"] + assert_equal "text/plain", details["content_type"] + assert_match %r{#{@config[:storage_account_name]}\.blob\.core\.windows\.net/#{@config[:container]}}, details["direct_upload"]["url"] + assert_equal({ "Content-Type" => "text/plain", "Content-MD5" => checksum, "x-ms-blob-type" => "BlockBlob" }, details["direct_upload"]["headers"]) + end + end + end +else + puts "Skipping Azure Storage Direct Upload tests because no Azure Storage configuration was supplied" +end + +class ActiveStorage::DiskDirectUploadsControllerTest < ActionDispatch::IntegrationTest + test "creating new direct upload" do + checksum = Digest::MD5.base64digest("Hello") + + post rails_direct_uploads_url, params: { blob: { + filename: "hello.txt", byte_size: 6, checksum: checksum, content_type: "text/plain" } } + + @response.parsed_body.tap do |details| + assert_equal ActiveStorage::Blob.find(details["id"]), ActiveStorage::Blob.find_signed(details["signed_id"]) + assert_equal "hello.txt", details["filename"] + assert_equal 6, details["byte_size"] + assert_equal checksum, details["checksum"] + assert_equal "text/plain", details["content_type"] + assert_match(/rails\/active_storage\/disk/, details["direct_upload"]["url"]) + assert_equal({ "Content-Type" => "text/plain" }, details["direct_upload"]["headers"]) + end + end +end diff --git a/activestorage/test/controllers/disk_controller_test.rb b/activestorage/test/controllers/disk_controller_test.rb new file mode 100644 index 0000000000..940dbf5918 --- /dev/null +++ b/activestorage/test/controllers/disk_controller_test.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +require "test_helper" +require "database/setup" + +class ActiveStorage::DiskControllerTest < ActionDispatch::IntegrationTest + test "showing blob inline" do + blob = create_blob + + get blob.service_url + assert_equal "inline; filename=\"hello.txt\"; filename*=UTF-8''hello.txt", @response.headers["Content-Disposition"] + assert_equal "text/plain", @response.headers["Content-Type"] + end + + test "showing blob as attachment" do + blob = create_blob + + get blob.service_url(disposition: :attachment) + assert_equal "attachment; filename=\"hello.txt\"; filename*=UTF-8''hello.txt", @response.headers["Content-Disposition"] + assert_equal "text/plain", @response.headers["Content-Type"] + end + + + test "directly uploading blob with integrity" do + data = "Something else entirely!" + blob = create_blob_before_direct_upload byte_size: data.size, checksum: Digest::MD5.base64digest(data) + + put blob.service_url_for_direct_upload, params: data, headers: { "Content-Type" => "text/plain" } + assert_response :no_content + assert_equal data, blob.download + end + + test "directly uploading blob without integrity" do + data = "Something else entirely!" + blob = create_blob_before_direct_upload byte_size: data.size, checksum: Digest::MD5.base64digest("bad data") + + put blob.service_url_for_direct_upload, params: data + assert_response :unprocessable_entity + assert_not blob.service.exist?(blob.key) + end + + test "directly uploading blob with mismatched content type" do + data = "Something else entirely!" + blob = create_blob_before_direct_upload byte_size: data.size, checksum: Digest::MD5.base64digest(data) + + put blob.service_url_for_direct_upload, params: data, headers: { "Content-Type" => "application/octet-stream" } + assert_response :unprocessable_entity + assert_not blob.service.exist?(blob.key) + end + + test "directly uploading blob with mismatched content length" do + data = "Something else entirely!" + blob = create_blob_before_direct_upload byte_size: data.size - 1, checksum: Digest::MD5.base64digest(data) + + put blob.service_url_for_direct_upload, params: data, headers: { "Content-Type" => "text/plain" } + assert_response :unprocessable_entity + assert_not blob.service.exist?(blob.key) + end +end diff --git a/activestorage/test/controllers/previews_controller_test.rb b/activestorage/test/controllers/previews_controller_test.rb new file mode 100644 index 0000000000..704a466160 --- /dev/null +++ b/activestorage/test/controllers/previews_controller_test.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require "test_helper" +require "database/setup" + +class ActiveStorage::PreviewsControllerTest < ActionDispatch::IntegrationTest + setup do + @blob = create_file_blob filename: "report.pdf", content_type: "application/pdf" + end + + test "showing preview inline" do + get rails_blob_preview_url( + filename: @blob.filename, + signed_blob_id: @blob.signed_id, + variation_key: ActiveStorage::Variation.encode(resize: "100x100")) + + assert @blob.preview_image.attached? + assert_redirected_to(/report\.png\?.*disposition=inline/) + + image = read_image(@blob.preview_image.variant(resize: "100x100")) + assert_equal 77, image.width + assert_equal 100, image.height + end + + test "showing preview with invalid signed blob ID" do + get rails_blob_preview_url( + filename: @blob.filename, + signed_blob_id: "invalid", + variation_key: ActiveStorage::Variation.encode(resize: "100x100")) + + assert_response :not_found + end +end diff --git a/activestorage/test/controllers/variants_controller_test.rb b/activestorage/test/controllers/variants_controller_test.rb new file mode 100644 index 0000000000..a0642f9bed --- /dev/null +++ b/activestorage/test/controllers/variants_controller_test.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require "test_helper" +require "database/setup" + +class ActiveStorage::VariantsControllerTest < ActionDispatch::IntegrationTest + setup do + @blob = create_file_blob filename: "racecar.jpg" + end + + test "showing variant inline" do + get rails_blob_variation_url( + filename: @blob.filename, + signed_blob_id: @blob.signed_id, + variation_key: ActiveStorage::Variation.encode(resize: "100x100")) + + assert_redirected_to(/racecar\.jpg\?.*disposition=inline/) + + image = read_image(@blob.variant(resize: "100x100")) + assert_equal 100, image.width + assert_equal 67, image.height + end + + test "showing variant with invalid signed blob ID" do + get rails_blob_variation_url( + filename: @blob.filename, + signed_blob_id: "invalid", + variation_key: ActiveStorage::Variation.encode(resize: "100x100")) + + assert_response :not_found + end +end diff --git a/activestorage/test/database/create_users_migration.rb b/activestorage/test/database/create_users_migration.rb new file mode 100644 index 0000000000..fdba87cacf --- /dev/null +++ b/activestorage/test/database/create_users_migration.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class ActiveStorageCreateUsers < ActiveRecord::Migration[5.2] + def change + create_table :users do |t| + t.string :name + end + end +end diff --git a/activestorage/test/database/setup.rb b/activestorage/test/database/setup.rb new file mode 100644 index 0000000000..705650a25d --- /dev/null +++ b/activestorage/test/database/setup.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +require_relative "create_users_migration" + +ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:") +ActiveRecord::Migrator.migrate File.expand_path("../../db/migrate", __dir__) +ActiveStorageCreateUsers.migrate(:up) diff --git a/activestorage/test/dummy/Rakefile b/activestorage/test/dummy/Rakefile new file mode 100644 index 0000000000..c4f9523878 --- /dev/null +++ b/activestorage/test/dummy/Rakefile @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +require_relative "config/application" + +Rails.application.load_tasks diff --git a/activestorage/test/dummy/app/assets/config/manifest.js b/activestorage/test/dummy/app/assets/config/manifest.js new file mode 100644 index 0000000000..a8adebe722 --- /dev/null +++ b/activestorage/test/dummy/app/assets/config/manifest.js @@ -0,0 +1,5 @@ + +//= link_tree ../images +//= link_directory ../javascripts .js +//= link_directory ../stylesheets .css +//= link active_storage_manifest.js diff --git a/activestorage/test/dummy/app/assets/images/.keep b/activestorage/test/dummy/app/assets/images/.keep new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/activestorage/test/dummy/app/assets/images/.keep diff --git a/activestorage/test/dummy/app/assets/javascripts/application.js b/activestorage/test/dummy/app/assets/javascripts/application.js new file mode 100644 index 0000000000..e54c6461cc --- /dev/null +++ b/activestorage/test/dummy/app/assets/javascripts/application.js @@ -0,0 +1,13 @@ +// This is a manifest file that'll be compiled into application.js, which will include all the files +// listed below. +// +// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, +// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path. +// +// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the +// compiled file. JavaScript code in this file should be added after the last require_* statement. +// +// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details +// about supported directives. +// +//= require_tree . diff --git a/activestorage/test/dummy/app/assets/stylesheets/application.css b/activestorage/test/dummy/app/assets/stylesheets/application.css new file mode 100644 index 0000000000..0ebd7fe829 --- /dev/null +++ b/activestorage/test/dummy/app/assets/stylesheets/application.css @@ -0,0 +1,15 @@ +/* + * This is a manifest file that'll be compiled into application.css, which will include all the files + * listed below. + * + * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, + * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path. + * + * You're free to add application-wide styles to this file and they'll appear at the bottom of the + * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS + * files in this directory. Styles in this file should be added after the last require_* statement. + * It is generally better to create a new file per style scope. + * + *= require_tree . + *= require_self + */ diff --git a/activestorage/test/dummy/app/controllers/application_controller.rb b/activestorage/test/dummy/app/controllers/application_controller.rb new file mode 100644 index 0000000000..280cc28ce2 --- /dev/null +++ b/activestorage/test/dummy/app/controllers/application_controller.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class ApplicationController < ActionController::Base + protect_from_forgery with: :exception +end diff --git a/activestorage/test/dummy/app/controllers/concerns/.keep b/activestorage/test/dummy/app/controllers/concerns/.keep new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/activestorage/test/dummy/app/controllers/concerns/.keep diff --git a/activestorage/test/dummy/app/helpers/application_helper.rb b/activestorage/test/dummy/app/helpers/application_helper.rb new file mode 100644 index 0000000000..15b06f0f67 --- /dev/null +++ b/activestorage/test/dummy/app/helpers/application_helper.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +module ApplicationHelper +end diff --git a/activestorage/test/dummy/app/jobs/application_job.rb b/activestorage/test/dummy/app/jobs/application_job.rb new file mode 100644 index 0000000000..d92ffddcb5 --- /dev/null +++ b/activestorage/test/dummy/app/jobs/application_job.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +class ApplicationJob < ActiveJob::Base +end diff --git a/activestorage/test/dummy/app/models/application_record.rb b/activestorage/test/dummy/app/models/application_record.rb new file mode 100644 index 0000000000..71fbba5b32 --- /dev/null +++ b/activestorage/test/dummy/app/models/application_record.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class ApplicationRecord < ActiveRecord::Base + self.abstract_class = true +end diff --git a/activestorage/test/dummy/app/models/concerns/.keep b/activestorage/test/dummy/app/models/concerns/.keep new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/activestorage/test/dummy/app/models/concerns/.keep diff --git a/activestorage/test/dummy/app/views/layouts/application.html.erb b/activestorage/test/dummy/app/views/layouts/application.html.erb new file mode 100644 index 0000000000..a6eb0174b7 --- /dev/null +++ b/activestorage/test/dummy/app/views/layouts/application.html.erb @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<html> + <head> + <title>Dummy</title> + <%= csrf_meta_tags %> + + <%= stylesheet_link_tag 'application', media: 'all' %> + <%= javascript_include_tag 'application' %> + </head> + + <body> + <%= yield %> + </body> +</html> diff --git a/activestorage/test/dummy/bin/bundle b/activestorage/test/dummy/bin/bundle new file mode 100755 index 0000000000..5015ba6f8b --- /dev/null +++ b/activestorage/test/dummy/bin/bundle @@ -0,0 +1,5 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) +load Gem.bin_path("bundler", "bundle") diff --git a/activestorage/test/dummy/bin/rails b/activestorage/test/dummy/bin/rails new file mode 100755 index 0000000000..22f2d8deee --- /dev/null +++ b/activestorage/test/dummy/bin/rails @@ -0,0 +1,6 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +APP_PATH = File.expand_path("../config/application", __dir__) +require_relative "../config/boot" +require "rails/commands" diff --git a/activestorage/test/dummy/bin/rake b/activestorage/test/dummy/bin/rake new file mode 100755 index 0000000000..e436ea54a1 --- /dev/null +++ b/activestorage/test/dummy/bin/rake @@ -0,0 +1,6 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require_relative "../config/boot" +require "rake" +Rake.application.run diff --git a/activestorage/test/dummy/bin/yarn b/activestorage/test/dummy/bin/yarn new file mode 100755 index 0000000000..c9b7498378 --- /dev/null +++ b/activestorage/test/dummy/bin/yarn @@ -0,0 +1,13 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +VENDOR_PATH = File.expand_path("..", __dir__) +Dir.chdir(VENDOR_PATH) do + begin + exec "yarnpkg #{ARGV.join(" ")}" + rescue Errno::ENOENT + $stderr.puts "Yarn executable was not detected in the system." + $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install" + exit 1 + end +end diff --git a/activestorage/test/dummy/config.ru b/activestorage/test/dummy/config.ru new file mode 100644 index 0000000000..bff88d608a --- /dev/null +++ b/activestorage/test/dummy/config.ru @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +# This file is used by Rack-based servers to start the application. + +require_relative "config/environment" + +run Rails.application diff --git a/activestorage/test/dummy/config/application.rb b/activestorage/test/dummy/config/application.rb new file mode 100644 index 0000000000..bd14ac0b1a --- /dev/null +++ b/activestorage/test/dummy/config/application.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require_relative "boot" + +require "rails" +require "active_model/railtie" +require "active_job/railtie" +require "active_record/railtie" +require "action_controller/railtie" +require "action_view/railtie" +require "sprockets/railtie" +require "active_storage/engine" + +Bundler.require(*Rails.groups) + +module Dummy + class Application < Rails::Application + config.load_defaults 5.2 + + config.active_storage.service = :local + end +end diff --git a/activestorage/test/dummy/config/boot.rb b/activestorage/test/dummy/config/boot.rb new file mode 100644 index 0000000000..72516d9255 --- /dev/null +++ b/activestorage/test/dummy/config/boot.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +# Set up gems listed in the Gemfile. +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../../Gemfile", __dir__) + +require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"]) +$LOAD_PATH.unshift File.expand_path("../../../lib", __dir__) + +if %w[s server c console].any? { |a| ARGV.include?(a) } + puts "=> Booting Rails" +end diff --git a/activestorage/test/dummy/config/database.yml b/activestorage/test/dummy/config/database.yml new file mode 100644 index 0000000000..0d02f24980 --- /dev/null +++ b/activestorage/test/dummy/config/database.yml @@ -0,0 +1,25 @@ +# SQLite version 3.x +# gem install sqlite3 +# +# Ensure the SQLite 3 gem is defined in your Gemfile +# gem 'sqlite3' +# +default: &default + adapter: sqlite3 + pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> + timeout: 5000 + +development: + <<: *default + database: db/development.sqlite3 + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + <<: *default + database: db/test.sqlite3 + +production: + <<: *default + database: db/production.sqlite3 diff --git a/activestorage/test/dummy/config/environment.rb b/activestorage/test/dummy/config/environment.rb new file mode 100644 index 0000000000..7df99e89c6 --- /dev/null +++ b/activestorage/test/dummy/config/environment.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +# Load the Rails application. +require_relative "application" + +# Initialize the Rails application. +Rails.application.initialize! diff --git a/activestorage/test/dummy/config/environments/development.rb b/activestorage/test/dummy/config/environments/development.rb new file mode 100644 index 0000000000..47fc5bf25c --- /dev/null +++ b/activestorage/test/dummy/config/environments/development.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # In the development environment your application's code is reloaded on + # every request. This slows down response time but is perfect for development + # since you don't have to restart the web server when you make code changes. + config.cache_classes = false + + # Do not eager load code on boot. + config.eager_load = false + + # Show full error reports. + config.consider_all_requests_local = true + + # Enable/disable caching. By default caching is disabled. + if Rails.root.join("tmp/caching-dev.txt").exist? + config.action_controller.perform_caching = true + + config.cache_store = :memory_store + config.public_file_server.headers = { + "Cache-Control" => "public, max-age=#{2.days.seconds.to_i}" + } + else + config.action_controller.perform_caching = false + + config.cache_store = :null_store + end + + # Print deprecation notices to the Rails logger. + config.active_support.deprecation = :log + + # Raise an error on page load if there are pending migrations. + config.active_record.migration_error = :page_load + + # Debug mode disables concatenation and preprocessing of assets. + # This option may cause significant delays in view rendering with a large + # number of complex assets. + config.assets.debug = true + + # Suppress logger output for asset requests. + config.assets.quiet = true + + # Raises error for missing translations + # config.action_view.raise_on_missing_translations = true + + # Use an evented file watcher to asynchronously detect changes in source code, + # routes, locales, etc. This feature depends on the listen gem. + # config.file_watcher = ActiveSupport::EventedFileUpdateChecker +end diff --git a/activestorage/test/dummy/config/environments/production.rb b/activestorage/test/dummy/config/environments/production.rb new file mode 100644 index 0000000000..34ac3bc561 --- /dev/null +++ b/activestorage/test/dummy/config/environments/production.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Code is not reloaded between requests. + config.cache_classes = true + + # Eager load code on boot. This eager loads most of Rails and + # your application in memory, allowing both threaded web servers + # and those relying on copy on write to perform better. + # Rake tasks automatically ignore this option for performance. + config.eager_load = true + + # Full error reports are disabled and caching is turned on. + config.consider_all_requests_local = false + config.action_controller.perform_caching = true + + # Attempt to read encrypted secrets from `config/secrets.yml.enc`. + # Requires an encryption key in `ENV["RAILS_MASTER_KEY"]` or + # `config/secrets.yml.key`. + config.read_encrypted_secrets = true + + # Disable serving static files from the `/public` folder by default since + # Apache or NGINX already handles this. + config.public_file_server.enabled = ENV["RAILS_SERVE_STATIC_FILES"].present? + + # Compress JavaScripts and CSS. + config.assets.js_compressor = :uglifier + # config.assets.css_compressor = :sass + + # Do not fallback to assets pipeline if a precompiled asset is missed. + config.assets.compile = false + + # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb + + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + # config.action_controller.asset_host = 'http://assets.example.com' + + # Specifies the header that your server uses for sending files. + # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache + # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX + + # Mount Action Cable outside main process or domain + # config.action_cable.mount_path = nil + # config.action_cable.url = 'wss://example.com/cable' + # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] + + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. + # config.force_ssl = true + + # Use the lowest log level to ensure availability of diagnostic information + # when problems arise. + config.log_level = :debug + + # Prepend all log lines with the following tags. + config.log_tags = [ :request_id ] + + # Use a different cache store in production. + # config.cache_store = :mem_cache_store + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation cannot be found). + config.i18n.fallbacks = true + + # Send deprecation notices to registered listeners. + config.active_support.deprecation = :notify + + # Use default logging formatter so that PID and timestamp are not suppressed. + config.log_formatter = ::Logger::Formatter.new + + # Use a different logger for distributed setups. + # require 'syslog/logger' + # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') + + if ENV["RAILS_LOG_TO_STDOUT"].present? + logger = ActiveSupport::Logger.new(STDOUT) + logger.formatter = config.log_formatter + config.logger = ActiveSupport::TaggedLogging.new(logger) + end + + # Do not dump schema after migrations. + config.active_record.dump_schema_after_migration = false +end diff --git a/activestorage/test/dummy/config/environments/test.rb b/activestorage/test/dummy/config/environments/test.rb new file mode 100644 index 0000000000..74a802d98c --- /dev/null +++ b/activestorage/test/dummy/config/environments/test.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # The test environment is used exclusively to run your application's + # test suite. You never need to work with it otherwise. Remember that + # your test database is "scratch space" for the test suite and is wiped + # and recreated between test runs. Don't rely on the data there! + config.cache_classes = true + + # Do not eager load code on boot. This avoids loading your whole application + # just for the purpose of running a single test. If you are using a tool that + # preloads Rails for running tests, you may have to set it to true. + config.eager_load = false + + # Configure public file server for tests with Cache-Control for performance. + config.public_file_server.enabled = true + config.public_file_server.headers = { + "Cache-Control" => "public, max-age=#{1.hour.seconds.to_i}" + } + + # Show full error reports and disable caching. + config.consider_all_requests_local = true + config.action_controller.perform_caching = false + + # Raise exceptions instead of rendering exception templates. + config.action_dispatch.show_exceptions = false + + # Print deprecation notices to the stderr. + config.active_support.deprecation = :stderr + + # Disable request forgery protection in test environment. + config.action_controller.allow_forgery_protection = false + + # Raises error for missing translations + # config.action_view.raise_on_missing_translations = true +end diff --git a/activestorage/test/dummy/config/initializers/application_controller_renderer.rb b/activestorage/test/dummy/config/initializers/application_controller_renderer.rb new file mode 100644 index 0000000000..315ac48a9a --- /dev/null +++ b/activestorage/test/dummy/config/initializers/application_controller_renderer.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true +# Be sure to restart your server when you modify this file. + +# ApplicationController.renderer.defaults.merge!( +# http_host: 'example.org', +# https: false +# ) diff --git a/activestorage/test/dummy/config/initializers/assets.rb b/activestorage/test/dummy/config/initializers/assets.rb new file mode 100644 index 0000000000..ba194685a2 --- /dev/null +++ b/activestorage/test/dummy/config/initializers/assets.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +# Be sure to restart your server when you modify this file. + +# Version of your assets, change this if you want to expire all your assets. +Rails.application.config.assets.version = "1.0" + +# Add additional assets to the asset load path. +# Rails.application.config.assets.paths << Emoji.images_path +# Add Yarn node_modules folder to the asset load path. +Rails.application.config.assets.paths << Rails.root.join("node_modules") + +# Precompile additional assets. +# application.js, application.css, and all non-JS/CSS in the app/assets +# folder are already added. +# Rails.application.config.assets.precompile += %w( admin.js admin.css ) diff --git a/activestorage/test/dummy/config/initializers/backtrace_silencers.rb b/activestorage/test/dummy/config/initializers/backtrace_silencers.rb new file mode 100644 index 0000000000..d0f0d3b5df --- /dev/null +++ b/activestorage/test/dummy/config/initializers/backtrace_silencers.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true +# Be sure to restart your server when you modify this file. + +# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. +# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } + +# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. +# Rails.backtrace_cleaner.remove_silencers! diff --git a/activestorage/test/dummy/config/initializers/cookies_serializer.rb b/activestorage/test/dummy/config/initializers/cookies_serializer.rb new file mode 100644 index 0000000000..ee8dff9c99 --- /dev/null +++ b/activestorage/test/dummy/config/initializers/cookies_serializer.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +# Be sure to restart your server when you modify this file. + +# Specify a serializer for the signed and encrypted cookie jars. +# Valid options are :json, :marshal, and :hybrid. +Rails.application.config.action_dispatch.cookies_serializer = :json diff --git a/activestorage/test/dummy/config/initializers/filter_parameter_logging.rb b/activestorage/test/dummy/config/initializers/filter_parameter_logging.rb new file mode 100644 index 0000000000..7a4f47b4c2 --- /dev/null +++ b/activestorage/test/dummy/config/initializers/filter_parameter_logging.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +# Be sure to restart your server when you modify this file. + +# Configure sensitive parameters which will be filtered from the log file. +Rails.application.config.filter_parameters += [:password] diff --git a/activestorage/test/dummy/config/initializers/inflections.rb b/activestorage/test/dummy/config/initializers/inflections.rb new file mode 100644 index 0000000000..aa7435fbc9 --- /dev/null +++ b/activestorage/test/dummy/config/initializers/inflections.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true +# Be sure to restart your server when you modify this file. + +# Add new inflection rules using the following format. Inflections +# are locale specific, and you may define rules for as many different +# locales as you wish. All of these examples are active by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.plural /^(ox)$/i, '\1en' +# inflect.singular /^(ox)en/i, '\1' +# inflect.irregular 'person', 'people' +# inflect.uncountable %w( fish sheep ) +# end + +# These inflection rules are supported but not enabled by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.acronym 'RESTful' +# end diff --git a/activestorage/test/dummy/config/initializers/mime_types.rb b/activestorage/test/dummy/config/initializers/mime_types.rb new file mode 100644 index 0000000000..6e1d16f027 --- /dev/null +++ b/activestorage/test/dummy/config/initializers/mime_types.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true +# Be sure to restart your server when you modify this file. + +# Add new mime types for use in respond_to blocks: +# Mime::Type.register "text/richtext", :rtf diff --git a/activestorage/test/dummy/config/initializers/wrap_parameters.rb b/activestorage/test/dummy/config/initializers/wrap_parameters.rb new file mode 100644 index 0000000000..2f3c0db471 --- /dev/null +++ b/activestorage/test/dummy/config/initializers/wrap_parameters.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +# Be sure to restart your server when you modify this file. + +# This file contains settings for ActionController::ParamsWrapper which +# is enabled by default. + +# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. +ActiveSupport.on_load(:action_controller) do + wrap_parameters format: [:json] +end + +# To enable root element in JSON for ActiveRecord objects. +# ActiveSupport.on_load(:active_record) do +# self.include_root_in_json = true +# end diff --git a/activestorage/test/dummy/config/routes.rb b/activestorage/test/dummy/config/routes.rb new file mode 100644 index 0000000000..edf04d2d63 --- /dev/null +++ b/activestorage/test/dummy/config/routes.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +Rails.application.routes.draw do +end diff --git a/activestorage/test/dummy/config/secrets.yml b/activestorage/test/dummy/config/secrets.yml new file mode 100644 index 0000000000..77d1fc383a --- /dev/null +++ b/activestorage/test/dummy/config/secrets.yml @@ -0,0 +1,32 @@ +# Be sure to restart your server when you modify this file. + +# Your secret key is used for verifying the integrity of signed cookies. +# If you change this key, all old signed cookies will become invalid! + +# Make sure the secret is at least 30 characters and all random, +# no regular words or you'll be exposed to dictionary attacks. +# You can use `rails secret` to generate a secure secret key. + +# Make sure the secrets in this file are kept private +# if you're sharing your code publicly. + +# Shared secrets are available across all environments. + +# shared: +# api_key: a1B2c3D4e5F6 + +# Environmental secrets are only available for that specific environment. + +development: + secret_key_base: e0ef5744b10d988669be6b2660c259749779964f3dcb487fd6199743b3558e2d89f7681d6a15d16d144e28979cbdae41885f4fb4c2cf56ff92ac22df282ffb66 + +test: + secret_key_base: 6fb1f3a828a8dcd6ac8dc07b43be4a5265ad64379120d417252a1578fe1f790e7b85ade4f95994de1ac8fb78581690de6e3a6ac4af36a0f0139667418c750d05 + +# Do not keep production secrets in the unencrypted secrets file. +# Instead, either read values from the environment. +# Or, use `bin/rails secrets:setup` to configure encrypted secrets +# and move the `production:` environment over there. + +production: + secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> diff --git a/activestorage/test/dummy/config/spring.rb b/activestorage/test/dummy/config/spring.rb new file mode 100644 index 0000000000..ff5ba06b6d --- /dev/null +++ b/activestorage/test/dummy/config/spring.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +%w( + .ruby-version + .rbenv-vars + tmp/restart.txt + tmp/caching-dev.txt +).each { |path| Spring.watch(path) } diff --git a/activestorage/test/dummy/config/storage.yml b/activestorage/test/dummy/config/storage.yml new file mode 100644 index 0000000000..2c6762e0d6 --- /dev/null +++ b/activestorage/test/dummy/config/storage.yml @@ -0,0 +1,3 @@ +local: + service: Disk + root: <%= Rails.root.join("storage") %> diff --git a/activestorage/test/dummy/lib/assets/.keep b/activestorage/test/dummy/lib/assets/.keep new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/activestorage/test/dummy/lib/assets/.keep diff --git a/activestorage/test/dummy/log/.keep b/activestorage/test/dummy/log/.keep new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/activestorage/test/dummy/log/.keep diff --git a/activestorage/test/dummy/package.json b/activestorage/test/dummy/package.json new file mode 100644 index 0000000000..caa2d7bb3f --- /dev/null +++ b/activestorage/test/dummy/package.json @@ -0,0 +1,5 @@ +{ + "name": "dummy", + "private": true, + "dependencies": {} +} diff --git a/activestorage/test/dummy/public/404.html b/activestorage/test/dummy/public/404.html new file mode 100644 index 0000000000..2be3af26fc --- /dev/null +++ b/activestorage/test/dummy/public/404.html @@ -0,0 +1,67 @@ +<!DOCTYPE html> +<html> +<head> + <title>The page you were looking for doesn't exist (404)</title> + <meta name="viewport" content="width=device-width,initial-scale=1"> + <style> + .rails-default-error-page { + background-color: #EFEFEF; + color: #2E2F30; + text-align: center; + font-family: arial, sans-serif; + margin: 0; + } + + .rails-default-error-page div.dialog { + width: 95%; + max-width: 33em; + margin: 4em auto 0; + } + + .rails-default-error-page div.dialog > div { + border: 1px solid #CCC; + border-right-color: #999; + border-left-color: #999; + border-bottom-color: #BBB; + border-top: #B00100 solid 4px; + border-top-left-radius: 9px; + border-top-right-radius: 9px; + background-color: white; + padding: 7px 12% 0; + box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17); + } + + .rails-default-error-page h1 { + font-size: 100%; + color: #730E15; + line-height: 1.5em; + } + + .rails-default-error-page div.dialog > p { + margin: 0 0 1em; + padding: 1em; + background-color: #F7F7F7; + border: 1px solid #CCC; + border-right-color: #999; + border-left-color: #999; + border-bottom-color: #999; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + border-top-color: #DADADA; + color: #666; + box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17); + } + </style> +</head> + +<body class="rails-default-error-page"> + <!-- This file lives in public/404.html --> + <div class="dialog"> + <div> + <h1>The page you were looking for doesn't exist.</h1> + <p>You may have mistyped the address or the page may have moved.</p> + </div> + <p>If you are the application owner check the logs for more information.</p> + </div> +</body> +</html> diff --git a/activestorage/test/dummy/public/422.html b/activestorage/test/dummy/public/422.html new file mode 100644 index 0000000000..c08eac0d1d --- /dev/null +++ b/activestorage/test/dummy/public/422.html @@ -0,0 +1,67 @@ +<!DOCTYPE html> +<html> +<head> + <title>The change you wanted was rejected (422)</title> + <meta name="viewport" content="width=device-width,initial-scale=1"> + <style> + .rails-default-error-page { + background-color: #EFEFEF; + color: #2E2F30; + text-align: center; + font-family: arial, sans-serif; + margin: 0; + } + + .rails-default-error-page div.dialog { + width: 95%; + max-width: 33em; + margin: 4em auto 0; + } + + .rails-default-error-page div.dialog > div { + border: 1px solid #CCC; + border-right-color: #999; + border-left-color: #999; + border-bottom-color: #BBB; + border-top: #B00100 solid 4px; + border-top-left-radius: 9px; + border-top-right-radius: 9px; + background-color: white; + padding: 7px 12% 0; + box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17); + } + + .rails-default-error-page h1 { + font-size: 100%; + color: #730E15; + line-height: 1.5em; + } + + .rails-default-error-page div.dialog > p { + margin: 0 0 1em; + padding: 1em; + background-color: #F7F7F7; + border: 1px solid #CCC; + border-right-color: #999; + border-left-color: #999; + border-bottom-color: #999; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + border-top-color: #DADADA; + color: #666; + box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17); + } + </style> +</head> + +<body class="rails-default-error-page"> + <!-- This file lives in public/422.html --> + <div class="dialog"> + <div> + <h1>The change you wanted was rejected.</h1> + <p>Maybe you tried to change something you didn't have access to.</p> + </div> + <p>If you are the application owner check the logs for more information.</p> + </div> +</body> +</html> diff --git a/activestorage/test/dummy/public/500.html b/activestorage/test/dummy/public/500.html new file mode 100644 index 0000000000..78a030af22 --- /dev/null +++ b/activestorage/test/dummy/public/500.html @@ -0,0 +1,66 @@ +<!DOCTYPE html> +<html> +<head> + <title>We're sorry, but something went wrong (500)</title> + <meta name="viewport" content="width=device-width,initial-scale=1"> + <style> + .rails-default-error-page { + background-color: #EFEFEF; + color: #2E2F30; + text-align: center; + font-family: arial, sans-serif; + margin: 0; + } + + .rails-default-error-page div.dialog { + width: 95%; + max-width: 33em; + margin: 4em auto 0; + } + + .rails-default-error-page div.dialog > div { + border: 1px solid #CCC; + border-right-color: #999; + border-left-color: #999; + border-bottom-color: #BBB; + border-top: #B00100 solid 4px; + border-top-left-radius: 9px; + border-top-right-radius: 9px; + background-color: white; + padding: 7px 12% 0; + box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17); + } + + .rails-default-error-page h1 { + font-size: 100%; + color: #730E15; + line-height: 1.5em; + } + + .rails-default-error-page div.dialog > p { + margin: 0 0 1em; + padding: 1em; + background-color: #F7F7F7; + border: 1px solid #CCC; + border-right-color: #999; + border-left-color: #999; + border-bottom-color: #999; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + border-top-color: #DADADA; + color: #666; + box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17); + } + </style> +</head> + +<body class="rails-default-error-page"> + <!-- This file lives in public/500.html --> + <div class="dialog"> + <div> + <h1>We're sorry, but something went wrong.</h1> + </div> + <p>If you are the application owner check the logs for more information.</p> + </div> +</body> +</html> diff --git a/activestorage/test/dummy/public/apple-touch-icon-precomposed.png b/activestorage/test/dummy/public/apple-touch-icon-precomposed.png new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/activestorage/test/dummy/public/apple-touch-icon-precomposed.png diff --git a/activestorage/test/dummy/public/apple-touch-icon.png b/activestorage/test/dummy/public/apple-touch-icon.png new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/activestorage/test/dummy/public/apple-touch-icon.png diff --git a/activestorage/test/dummy/public/favicon.ico b/activestorage/test/dummy/public/favicon.ico new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/activestorage/test/dummy/public/favicon.ico diff --git a/activestorage/test/fixtures/files/icon.psd b/activestorage/test/fixtures/files/icon.psd Binary files differnew file mode 100644 index 0000000000..631fceeaab --- /dev/null +++ b/activestorage/test/fixtures/files/icon.psd diff --git a/activestorage/test/fixtures/files/icon.svg b/activestorage/test/fixtures/files/icon.svg new file mode 100644 index 0000000000..6cfb0e241e --- /dev/null +++ b/activestorage/test/fixtures/files/icon.svg @@ -0,0 +1,13 @@ +<!-- The XML declaration is intentionally omitted. --> +<svg width="792px" height="584px" viewBox="0 0 792 584" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <path d="M9.51802657,28.724593 C9.51802657,18.2155955 18.1343454,9.60822622 28.6542694,9.60822622 L763.245541,9.60822622 C773.765465,9.60822622 782.381784,18.2155955 782.381784,28.724593 L782.381784,584 L792,584 L792,28.724593 C792,12.911054 779.075522,0 763.245541,0 L28.7544592,0 C12.9244782,0 0,12.911054 0,28.724593 L0,584 L9.61821632,584 C9.51802657,584 9.51802657,28.724593 9.51802657,28.724593 L9.51802657,28.724593 Z" id="Shape" opacity="0.3" fill="#CCCCCC"></path> + <circle id="Oval" fill="#FFCC33" cx="119.1" cy="147.2" r="33"></circle> + <circle id="Oval" fill="#3399FF" cx="119.1" cy="281.1" r="33"></circle> + <circle id="Oval" fill="#FF3333" cx="676.1" cy="376.8" r="33"></circle> + <circle id="Oval" fill="#FFCC33" cx="119.1" cy="477.2" r="33"></circle> + <rect id="Rectangle-path" opacity="0.75" fill="#CCCCCC" x="176.5" y="130.5" width="442.1" height="33.5"></rect> + <rect id="Rectangle-path" opacity="0.75" fill="#CCCCCC" x="176.5" y="183.1" width="442.1" height="33.5"></rect> + <rect id="Rectangle-path" opacity="0.75" fill="#CCCCCC" x="176.5" y="265.8" width="442.1" height="33.5"></rect> + <rect id="Rectangle-path" opacity="0.75" fill="#CCCCCC" x="176.5" y="363.4" width="442.1" height="33.5"></rect> + <rect id="Rectangle-path" opacity="0.75" fill="#CCCCCC" x="176.5" y="465.3" width="442.1" height="33.5"></rect> +</svg> diff --git a/activestorage/test/fixtures/files/image.gif b/activestorage/test/fixtures/files/image.gif Binary files differnew file mode 100644 index 0000000000..90c05f671c --- /dev/null +++ b/activestorage/test/fixtures/files/image.gif diff --git a/activestorage/test/fixtures/files/racecar.jpg b/activestorage/test/fixtures/files/racecar.jpg Binary files differnew file mode 100644 index 0000000000..934b4caa22 --- /dev/null +++ b/activestorage/test/fixtures/files/racecar.jpg diff --git a/activestorage/test/fixtures/files/report.pdf b/activestorage/test/fixtures/files/report.pdf Binary files differnew file mode 100644 index 0000000000..cccb9b5d64 --- /dev/null +++ b/activestorage/test/fixtures/files/report.pdf diff --git a/activestorage/test/fixtures/files/rotated_video.mp4 b/activestorage/test/fixtures/files/rotated_video.mp4 Binary files differnew file mode 100644 index 0000000000..4c7a4e9e57 --- /dev/null +++ b/activestorage/test/fixtures/files/rotated_video.mp4 diff --git a/activestorage/test/fixtures/files/video.mp4 b/activestorage/test/fixtures/files/video.mp4 Binary files differnew file mode 100644 index 0000000000..8fb1c5b24d --- /dev/null +++ b/activestorage/test/fixtures/files/video.mp4 diff --git a/activestorage/test/fixtures/files/video_without_video_stream.mp4 b/activestorage/test/fixtures/files/video_without_video_stream.mp4 Binary files differnew file mode 100644 index 0000000000..e6a55f868b --- /dev/null +++ b/activestorage/test/fixtures/files/video_without_video_stream.mp4 diff --git a/activestorage/test/models/attachments_test.rb b/activestorage/test/models/attachments_test.rb new file mode 100644 index 0000000000..20eec3c220 --- /dev/null +++ b/activestorage/test/models/attachments_test.rb @@ -0,0 +1,347 @@ +# frozen_string_literal: true + +require "test_helper" +require "database/setup" + +class ActiveStorage::AttachmentsTest < ActiveSupport::TestCase + include ActiveJob::TestHelper + + setup { @user = User.create!(name: "DHH") } + + teardown { ActiveStorage::Blob.all.each(&:purge) } + + test "attach existing blob" do + @user.avatar.attach create_blob(filename: "funky.jpg") + assert_equal "funky.jpg", @user.avatar.filename.to_s + end + + test "attach existing blob from a signed ID" do + @user.avatar.attach create_blob(filename: "funky.jpg").signed_id + assert_equal "funky.jpg", @user.avatar.filename.to_s + end + + test "attach new blob from a Hash" do + @user.avatar.attach io: StringIO.new("STUFF"), filename: "town.jpg", content_type: "image/jpg" + assert_equal "town.jpg", @user.avatar.filename.to_s + end + + test "attach new blob from an UploadedFile" do + file = file_fixture "racecar.jpg" + @user.avatar.attach Rack::Test::UploadedFile.new file.to_s + assert_equal "racecar.jpg", @user.avatar.filename.to_s + end + + test "replace attached blob" do + @user.avatar.attach create_blob(filename: "funky.jpg") + + perform_enqueued_jobs do + assert_no_difference -> { ActiveStorage::Blob.count } do + @user.avatar.attach create_blob(filename: "town.jpg") + end + end + + assert_equal "town.jpg", @user.avatar.filename.to_s + end + + test "replace attached blob unsuccessfully" do + @user.avatar.attach create_blob(filename: "funky.jpg") + + perform_enqueued_jobs do + assert_raises do + @user.avatar.attach nil + end + end + + assert_equal "funky.jpg", @user.reload.avatar.filename.to_s + assert ActiveStorage::Blob.service.exist?(@user.avatar.key) + end + + test "attach blob to new record" do + user = User.new(name: "Jason") + + assert_no_changes -> { user.new_record? } do + assert_no_difference -> { ActiveStorage::Attachment.count } do + user.avatar.attach create_blob(filename: "funky.jpg") + end + end + + assert user.avatar.attached? + assert_equal "funky.jpg", user.avatar.filename.to_s + + assert_difference -> { ActiveStorage::Attachment.count }, +1 do + user.save! + end + + assert user.reload.avatar.attached? + assert_equal "funky.jpg", user.avatar.filename.to_s + end + + test "build new record with attached blob" do + assert_no_difference -> { ActiveStorage::Attachment.count } do + @user = User.new(name: "Jason", avatar: { io: StringIO.new("STUFF"), filename: "town.jpg", content_type: "image/jpg" }) + end + + assert @user.new_record? + assert @user.avatar.attached? + assert_equal "town.jpg", @user.avatar.filename.to_s + + @user.save! + assert @user.reload.avatar.attached? + assert_equal "town.jpg", @user.avatar.filename.to_s + end + + test "access underlying associations of new blob" do + @user.avatar.attach create_blob(filename: "funky.jpg") + assert_equal @user, @user.avatar_attachment.record + assert_equal @user.avatar_attachment.blob, @user.avatar_blob + assert_equal "funky.jpg", @user.avatar_attachment.blob.filename.to_s + end + + test "analyze newly-attached blob" do + perform_enqueued_jobs do + @user.avatar.attach create_file_blob + end + + assert_equal 4104, @user.avatar.reload.metadata[:width] + assert_equal 2736, @user.avatar.metadata[:height] + end + + test "analyze attached blob only once" do + blob = create_file_blob + + perform_enqueued_jobs do + @user.avatar.attach blob + end + + assert blob.reload.analyzed? + + @user.avatar.attachment.destroy + + assert_no_enqueued_jobs do + @user.reload.avatar.attach blob + end + end + + test "preserve existing metadata when analyzing a newly-attached blob" do + blob = create_file_blob(metadata: { foo: "bar" }) + + perform_enqueued_jobs do + @user.avatar.attach blob + end + + assert_equal "bar", blob.reload.metadata[:foo] + end + + test "detach blob" do + @user.avatar.attach create_blob(filename: "funky.jpg") + avatar_blob_id = @user.avatar.blob.id + avatar_key = @user.avatar.key + + @user.avatar.detach + assert_not @user.avatar.attached? + assert ActiveStorage::Blob.exists?(avatar_blob_id) + assert ActiveStorage::Blob.service.exist?(avatar_key) + end + + test "purge attached blob" do + @user.avatar.attach create_blob(filename: "funky.jpg") + avatar_key = @user.avatar.key + + @user.avatar.purge + assert_not @user.avatar.attached? + assert_not ActiveStorage::Blob.service.exist?(avatar_key) + end + + test "purge attached blob later when the record is destroyed" do + @user.avatar.attach create_blob(filename: "funky.jpg") + avatar_key = @user.avatar.key + + perform_enqueued_jobs do + @user.destroy + + assert_nil ActiveStorage::Blob.find_by(key: avatar_key) + assert_not ActiveStorage::Blob.service.exist?(avatar_key) + end + end + + test "find with attached blob" do + records = %w[alice bob].map do |name| + User.create!(name: name).tap do |user| + user.avatar.attach create_blob(filename: "#{name}.jpg") + end + end + + users = User.where(id: records.map(&:id)).with_attached_avatar.all + + assert_equal "alice.jpg", users.first.avatar.filename.to_s + assert_equal "bob.jpg", users.second.avatar.filename.to_s + end + + + test "attach existing blobs" do + @user.highlights.attach create_blob(filename: "funky.jpg"), create_blob(filename: "wonky.jpg") + + assert_equal "funky.jpg", @user.highlights.first.filename.to_s + assert_equal "wonky.jpg", @user.highlights.second.filename.to_s + end + + test "attach new blobs" do + @user.highlights.attach( + { io: StringIO.new("STUFF"), filename: "town.jpg", content_type: "image/jpg" }, + { io: StringIO.new("IT"), filename: "country.jpg", content_type: "image/jpg" }) + + assert_equal "town.jpg", @user.highlights.first.filename.to_s + assert_equal "country.jpg", @user.highlights.second.filename.to_s + end + + test "attach blobs to new record" do + user = User.new(name: "Jason") + + assert_no_changes -> { user.new_record? } do + assert_no_difference -> { ActiveStorage::Attachment.count } do + user.highlights.attach( + { io: StringIO.new("STUFF"), filename: "town.jpg", content_type: "image/jpg" }, + { io: StringIO.new("IT"), filename: "country.jpg", content_type: "image/jpg" }) + end + end + + assert user.highlights.attached? + assert_equal "town.jpg", user.highlights.first.filename.to_s + assert_equal "country.jpg", user.highlights.second.filename.to_s + + assert_difference -> { ActiveStorage::Attachment.count }, +2 do + user.save! + end + + assert user.reload.highlights.attached? + assert_equal "town.jpg", user.highlights.first.filename.to_s + assert_equal "country.jpg", user.highlights.second.filename.to_s + end + + test "build new record with attached blobs" do + assert_no_difference -> { ActiveStorage::Attachment.count } do + @user = User.new(name: "Jason", highlights: [ + { io: StringIO.new("STUFF"), filename: "town.jpg", content_type: "image/jpg" }, + { io: StringIO.new("IT"), filename: "country.jpg", content_type: "image/jpg" }]) + end + + assert @user.new_record? + assert @user.highlights.attached? + assert_equal "town.jpg", @user.highlights.first.filename.to_s + assert_equal "country.jpg", @user.highlights.second.filename.to_s + + @user.save! + assert @user.reload.highlights.attached? + assert_equal "town.jpg", @user.highlights.first.filename.to_s + assert_equal "country.jpg", @user.highlights.second.filename.to_s + end + + test "find attached blobs" do + @user.highlights.attach( + { io: StringIO.new("STUFF"), filename: "town.jpg", content_type: "image/jpg" }, + { io: StringIO.new("IT"), filename: "country.jpg", content_type: "image/jpg" }) + + highlights = User.where(id: @user.id).with_attached_highlights.first.highlights + + assert_equal "town.jpg", highlights.first.filename.to_s + assert_equal "country.jpg", highlights.second.filename.to_s + end + + test "access underlying associations of new blobs" do + @user.highlights.attach( + { io: StringIO.new("STUFF"), filename: "town.jpg", content_type: "image/jpg" }, + { io: StringIO.new("IT"), filename: "country.jpg", content_type: "image/jpg" }) + + assert_equal @user, @user.highlights_attachments.first.record + assert_equal @user.highlights_attachments.collect(&:blob).sort, @user.highlights_blobs.sort + assert_equal "town.jpg", @user.highlights_attachments.first.blob.filename.to_s + end + + test "analyze newly-attached blobs" do + perform_enqueued_jobs do + @user.highlights.attach( + create_file_blob(filename: "racecar.jpg", content_type: "image/jpeg"), + create_file_blob(filename: "video.mp4", content_type: "video/mp4")) + end + + assert_equal 4104, @user.highlights.first.metadata[:width] + assert_equal 2736, @user.highlights.first.metadata[:height] + + assert_equal 640, @user.highlights.second.metadata[:width] + assert_equal 480, @user.highlights.second.metadata[:height] + end + + test "analyze attached blobs only once" do + blobs = [ + create_file_blob(filename: "racecar.jpg", content_type: "image/jpeg"), + create_file_blob(filename: "video.mp4", content_type: "video/mp4") + ] + + perform_enqueued_jobs do + @user.highlights.attach(blobs) + end + + assert blobs.each(&:reload).all?(&:analyzed?) + + @user.highlights.attachments.destroy_all + + assert_no_enqueued_jobs do + @user.highlights.attach(blobs) + end + end + + test "preserve existing metadata when analyzing newly-attached blobs" do + blobs = [ + create_file_blob(filename: "racecar.jpg", content_type: "image/jpeg", metadata: { foo: "bar" }), + create_file_blob(filename: "video.mp4", content_type: "video/mp4", metadata: { foo: "bar" }) + ] + + perform_enqueued_jobs do + @user.highlights.attach(blobs) + end + + blobs.each do |blob| + assert_equal "bar", blob.reload.metadata[:foo] + end + end + + test "detach blobs" do + @user.highlights.attach create_blob(filename: "funky.jpg"), create_blob(filename: "wonky.jpg") + highlight_blob_ids = @user.highlights.collect { |highlight| highlight.blob.id } + highlight_keys = @user.highlights.collect(&:key) + + @user.highlights.detach + assert_not @user.highlights.attached? + + assert ActiveStorage::Blob.exists?(highlight_blob_ids.first) + assert ActiveStorage::Blob.exists?(highlight_blob_ids.second) + + assert ActiveStorage::Blob.service.exist?(highlight_keys.first) + assert ActiveStorage::Blob.service.exist?(highlight_keys.second) + end + + test "purge attached blobs" do + @user.highlights.attach create_blob(filename: "funky.jpg"), create_blob(filename: "wonky.jpg") + highlight_keys = @user.highlights.collect(&:key) + + @user.highlights.purge + assert_not @user.highlights.attached? + assert_not ActiveStorage::Blob.service.exist?(highlight_keys.first) + assert_not ActiveStorage::Blob.service.exist?(highlight_keys.second) + end + + test "purge attached blobs later when the record is destroyed" do + @user.highlights.attach create_blob(filename: "funky.jpg"), create_blob(filename: "wonky.jpg") + highlight_keys = @user.highlights.collect(&:key) + + perform_enqueued_jobs do + @user.destroy + + assert_nil ActiveStorage::Blob.find_by(key: highlight_keys.first) + assert_not ActiveStorage::Blob.service.exist?(highlight_keys.first) + + assert_nil ActiveStorage::Blob.find_by(key: highlight_keys.second) + assert_not ActiveStorage::Blob.service.exist?(highlight_keys.second) + end + end +end diff --git a/activestorage/test/models/blob_test.rb b/activestorage/test/models/blob_test.rb new file mode 100644 index 0000000000..2db516c088 --- /dev/null +++ b/activestorage/test/models/blob_test.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +require "test_helper" +require "database/setup" + +class ActiveStorage::BlobTest < ActiveSupport::TestCase + test "create after upload sets byte size and checksum" do + data = "Hello world!" + blob = create_blob data: data + + assert_equal data, blob.download + assert_equal data.length, blob.byte_size + assert_equal Digest::MD5.base64digest(data), blob.checksum + end + + test "text?" do + blob = create_blob data: "Hello world!" + assert blob.text? + assert_not blob.audio? + end + + test "download yields chunks" do + blob = create_blob data: "a" * 75.kilobytes + chunks = [] + + blob.download do |chunk| + chunks << chunk + end + + assert_equal 2, chunks.size + assert_equal "a" * 64.kilobytes, chunks.first + assert_equal "a" * 11.kilobytes, chunks.second + end + + test "urls expiring in 5 minutes" do + blob = create_blob + + freeze_time do + assert_equal expected_url_for(blob), blob.service_url + assert_equal expected_url_for(blob, disposition: :attachment), blob.service_url(disposition: :attachment) + end + end + + test "urls force attachment as content disposition for content types served as binary" do + blob = create_blob(content_type: "text/html") + + freeze_time do + assert_equal expected_url_for(blob, disposition: :attachment), blob.service_url + assert_equal expected_url_for(blob, disposition: :attachment), blob.service_url(disposition: :inline) + end + end + + test "urls allow for custom filename" do + blob = create_blob(filename: "original.txt") + new_filename = ActiveStorage::Filename.new("new.txt") + + freeze_time do + assert_equal expected_url_for(blob), blob.service_url + assert_equal expected_url_for(blob, filename: new_filename), blob.service_url(filename: new_filename) + end + end + + test "purge deletes file from external service" do + blob = create_blob + + blob.purge + assert_not ActiveStorage::Blob.service.exist?(blob.key) + end + + test "purge deletes variants from external service" do + blob = create_file_blob + variant = blob.variant(resize: "100>").processed + + blob.purge + assert_not ActiveStorage::Blob.service.exist?(variant.key) + end + + private + def expected_url_for(blob, disposition: :inline, filename: nil) + filename ||= blob.filename + query_string = { content_type: blob.content_type, disposition: "#{disposition}; #{filename.parameters}" }.to_param + "/rails/active_storage/disk/#{ActiveStorage.verifier.generate(blob.key, expires_in: 5.minutes, purpose: :blob_key)}/#{filename}?#{query_string}" + end +end diff --git a/activestorage/test/models/filename/parameters_test.rb b/activestorage/test/models/filename/parameters_test.rb new file mode 100644 index 0000000000..431be00639 --- /dev/null +++ b/activestorage/test/models/filename/parameters_test.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require "test_helper" + +class ActiveStorage::Filename::ParametersTest < ActiveSupport::TestCase + test "parameterizing a Latin filename" do + filename = ActiveStorage::Filename.new("racecar.jpg") + + assert_equal %(filename="racecar.jpg"), filename.parameters.ascii + assert_equal "filename*=UTF-8''racecar.jpg", filename.parameters.utf8 + assert_equal "#{filename.parameters.ascii}; #{filename.parameters.utf8}", filename.parameters.combined + assert_equal filename.parameters.combined, filename.parameters.to_s + end + + test "parameterizing a Latin filename with accented characters" do + filename = ActiveStorage::Filename.new("råcëçâr.jpg") + + assert_equal %(filename="racecar.jpg"), filename.parameters.ascii + assert_equal "filename*=UTF-8''r%C3%A5c%C3%AB%C3%A7%C3%A2r.jpg", filename.parameters.utf8 + assert_equal "#{filename.parameters.ascii}; #{filename.parameters.utf8}", filename.parameters.combined + assert_equal filename.parameters.combined, filename.parameters.to_s + end + + test "parameterizing a non-Latin filename" do + filename = ActiveStorage::Filename.new("автомобиль.jpg") + + assert_equal %(filename="%3F%3F%3F%3F%3F%3F%3F%3F%3F%3F.jpg"), filename.parameters.ascii + assert_equal "filename*=UTF-8''%D0%B0%D0%B2%D1%82%D0%BE%D0%BC%D0%BE%D0%B1%D0%B8%D0%BB%D1%8C.jpg", filename.parameters.utf8 + assert_equal "#{filename.parameters.ascii}; #{filename.parameters.utf8}", filename.parameters.combined + assert_equal filename.parameters.combined, filename.parameters.to_s + end +end diff --git a/activestorage/test/models/filename_test.rb b/activestorage/test/models/filename_test.rb new file mode 100644 index 0000000000..88405e41c0 --- /dev/null +++ b/activestorage/test/models/filename_test.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +require "test_helper" + +class ActiveStorage::FilenameTest < ActiveSupport::TestCase + test "base" do + assert_equal "racecar", ActiveStorage::Filename.new("racecar.jpg").base + assert_equal "race.car", ActiveStorage::Filename.new("race.car.jpg").base + assert_equal "racecar", ActiveStorage::Filename.new("racecar").base + end + + test "extension with delimiter" do + assert_equal ".jpg", ActiveStorage::Filename.new("racecar.jpg").extension_with_delimiter + assert_equal ".jpg", ActiveStorage::Filename.new("race.car.jpg").extension_with_delimiter + assert_equal "", ActiveStorage::Filename.new("racecar").extension_with_delimiter + end + + test "extension without delimiter" do + assert_equal "jpg", ActiveStorage::Filename.new("racecar.jpg").extension_without_delimiter + assert_equal "jpg", ActiveStorage::Filename.new("race.car.jpg").extension_without_delimiter + assert_equal "", ActiveStorage::Filename.new("racecar").extension_without_delimiter + end + + test "sanitize" do + "%$|:;/\t\r\n\\".each_char do |character| + filename = ActiveStorage::Filename.new("foo#{character}bar.pdf") + assert_equal "foo-bar.pdf", filename.sanitized + assert_equal "foo-bar.pdf", filename.to_s + end + end + + test "sanitize transcodes to valid UTF-8" do + { "\xF6".dup.force_encoding(Encoding::ISO8859_1) => "ö", + "\xC3".dup.force_encoding(Encoding::ISO8859_1) => "Ã", + "\xAD" => "�", + "\xCF" => "�", + "\x00" => "", + }.each do |actual, expected| + assert_equal expected, ActiveStorage::Filename.new(actual).sanitized + end + end + + test "strips RTL override chars used to spoof unsafe executables as docs" do + # Would be displayed in Windows as "evilexe.pdf" due to the right-to-left + # (RTL) override char! + assert_equal "evil-fdp.exe", ActiveStorage::Filename.new("evil\u{202E}fdp.exe").sanitized + end + + test "compare case-insensitively" do + assert_operator ActiveStorage::Filename.new("foobar.pdf"), :==, ActiveStorage::Filename.new("FooBar.PDF") + end + + test "compare sanitized" do + assert_operator ActiveStorage::Filename.new("foo-bar.pdf"), :==, ActiveStorage::Filename.new("foo\tbar.pdf") + end +end diff --git a/activestorage/test/models/preview_test.rb b/activestorage/test/models/preview_test.rb new file mode 100644 index 0000000000..b0c6d2c45b --- /dev/null +++ b/activestorage/test/models/preview_test.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require "test_helper" +require "database/setup" + +class ActiveStorage::PreviewTest < ActiveSupport::TestCase + test "previewing a PDF" do + blob = create_file_blob(filename: "report.pdf", content_type: "application/pdf") + preview = blob.preview(resize: "640x280").processed + + assert preview.image.attached? + assert_equal "report.png", preview.image.filename.to_s + assert_equal "image/png", preview.image.content_type + + image = read_image(preview.image) + assert_equal 612, image.width + assert_equal 792, image.height + end + + test "previewing an MP4 video" do + blob = create_file_blob(filename: "video.mp4", content_type: "video/mp4") + preview = blob.preview(resize: "640x280").processed + + assert preview.image.attached? + assert_equal "video.png", preview.image.filename.to_s + assert_equal "image/png", preview.image.content_type + + image = read_image(preview.image) + assert_equal 640, image.width + assert_equal 480, image.height + end + + test "previewing an unpreviewable blob" do + blob = create_file_blob + + assert_raises ActiveStorage::UnpreviewableError do + blob.preview resize: "640x280" + end + end +end diff --git a/activestorage/test/models/representation_test.rb b/activestorage/test/models/representation_test.rb new file mode 100644 index 0000000000..2a06b31c77 --- /dev/null +++ b/activestorage/test/models/representation_test.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require "test_helper" +require "database/setup" + +class ActiveStorage::RepresentationTest < ActiveSupport::TestCase + test "representing an image" do + blob = create_file_blob + representation = blob.representation(resize: "100x100").processed + + image = read_image(representation.image) + assert_equal 100, image.width + assert_equal 67, image.height + end + + test "representing a PDF" do + blob = create_file_blob(filename: "report.pdf", content_type: "application/pdf") + representation = blob.representation(resize: "640x280").processed + + image = read_image(representation.image) + assert_equal 612, image.width + assert_equal 792, image.height + end + + test "representing an MP4 video" do + blob = create_file_blob(filename: "video.mp4", content_type: "video/mp4") + representation = blob.representation(resize: "640x280").processed + + image = read_image(representation.image) + assert_equal 640, image.width + assert_equal 480, image.height + end + + test "representing an unrepresentable blob" do + blob = create_blob + + assert_raises ActiveStorage::UnrepresentableError do + blob.representation resize: "100x100" + end + end +end diff --git a/activestorage/test/models/variant_test.rb b/activestorage/test/models/variant_test.rb new file mode 100644 index 0000000000..49660e880a --- /dev/null +++ b/activestorage/test/models/variant_test.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +require "test_helper" +require "database/setup" + +class ActiveStorage::VariantTest < ActiveSupport::TestCase + test "resized variation of JPEG blob" do + blob = create_file_blob(filename: "racecar.jpg") + variant = blob.variant(resize: "100x100").processed + assert_match(/racecar\.jpg/, variant.service_url) + + image = read_image(variant) + assert_equal 100, image.width + assert_equal 67, image.height + end + + test "resized and monochrome variation of JPEG blob" do + blob = create_file_blob(filename: "racecar.jpg") + variant = blob.variant(resize: "100x100", monochrome: true).processed + assert_match(/racecar\.jpg/, variant.service_url) + + image = read_image(variant) + assert_equal 100, image.width + assert_equal 67, image.height + assert_match(/Gray/, image.colorspace) + end + + test "center-weighted crop of JPEG blob" do + blob = create_file_blob(filename: "racecar.jpg") + variant = blob.variant(combine_options: { + gravity: "center", + resize: "100x100^", + crop: "100x100+0+0", + }).processed + assert_match(/racecar\.jpg/, variant.service_url) + + image = read_image(variant) + assert_equal 100, image.width + assert_equal 100, image.height + end + + test "resized variation of PSD blob" do + blob = create_file_blob(filename: "icon.psd", content_type: "image/vnd.adobe.photoshop") + variant = blob.variant(resize: "20x20").processed + assert_match(/icon\.png/, variant.service_url) + + image = read_image(variant) + assert_equal "PNG", image.type + assert_equal 20, image.width + assert_equal 20, image.height + end + + test "optimized variation of GIF blob" do + blob = create_file_blob(filename: "image.gif", content_type: "image/gif") + + assert_nothing_raised do + blob.variant(layers: "Optimize").processed + end + end + + test "variation of invariable blob" do + assert_raises ActiveStorage::InvariableError do + create_file_blob(filename: "report.pdf", content_type: "application/pdf").variant(resize: "100x100") + end + end + + test "service_url doesn't grow in length despite long variant options" do + blob = create_file_blob(filename: "racecar.jpg") + variant = blob.variant(font: "a" * 10_000).processed + assert_operator variant.service_url.length, :<, 500 + end +end diff --git a/activestorage/test/previewer/pdf_previewer_test.rb b/activestorage/test/previewer/pdf_previewer_test.rb new file mode 100644 index 0000000000..fe32f39be4 --- /dev/null +++ b/activestorage/test/previewer/pdf_previewer_test.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require "test_helper" +require "database/setup" + +require "active_storage/previewer/pdf_previewer" + +class ActiveStorage::Previewer::PDFPreviewerTest < ActiveSupport::TestCase + setup do + @blob = create_file_blob(filename: "report.pdf", content_type: "application/pdf") + end + + test "previewing a PDF document" do + ActiveStorage::Previewer::PDFPreviewer.new(@blob).preview do |attachable| + assert_equal "image/png", attachable[:content_type] + assert_equal "report.png", attachable[:filename] + + image = MiniMagick::Image.read(attachable[:io]) + assert_equal 612, image.width + assert_equal 792, image.height + end + end +end diff --git a/activestorage/test/previewer/video_previewer_test.rb b/activestorage/test/previewer/video_previewer_test.rb new file mode 100644 index 0000000000..dba9b0d7e2 --- /dev/null +++ b/activestorage/test/previewer/video_previewer_test.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require "test_helper" +require "database/setup" + +require "active_storage/previewer/video_previewer" + +class ActiveStorage::Previewer::VideoPreviewerTest < ActiveSupport::TestCase + setup do + @blob = create_file_blob(filename: "video.mp4", content_type: "video/mp4") + end + + test "previewing an MP4 video" do + ActiveStorage::Previewer::VideoPreviewer.new(@blob).preview do |attachable| + assert_equal "image/png", attachable[:content_type] + assert_equal "video.png", attachable[:filename] + + image = MiniMagick::Image.read(attachable[:io]) + assert_equal 640, image.width + assert_equal 480, image.height + end + end +end 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..be31bbe858 --- /dev/null +++ b/activestorage/test/service/azure_storage_service_test.rb @@ -0,0 +1,22 @@ +# 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(FIXTURE_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 + 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..43cc013bc8 --- /dev/null +++ b/activestorage/test/service/configurations.example.yml @@ -0,0 +1,30 @@ +# 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 +# path: "" +# 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..df11aac161 --- /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..a2fd035e02 --- /dev/null +++ b/activestorage/test/service/configurator_test.rb @@ -0,0 +1,16 @@ +# 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 + 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..4a6361b920 --- /dev/null +++ b/activestorage/test/service/disk_service_test.rb @@ -0,0 +1,14 @@ +# 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(/rails\/active_storage\/disk\/.*\/avatar\.png\?content_type=image%2Fpng&disposition=inline/, + @service.url(FIXTURE_KEY, expires_in: 5.minutes, disposition: :inline, filename: ActiveStorage::Filename.new("avatar.png"), content_type: "image/png")) + 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..7efcd60fb7 --- /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", "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 "signed URL generation" do + assert_match(/storage\.googleapis\.com\/.*response-content-disposition=inline.*test\.txt.*response-content-type=text%2Fplain/, + @service.url(FIXTURE_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.header["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..92101b1282 --- /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 + data = "Something else entirely!" + key = upload(data, to: @service) + + 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 + data = "Something else entirely!" + key = upload(data, to: SERVICE.primary) + + assert_equal data, @service.download(key) + end + + test "deleting from all services" do + @service.delete FIXTURE_KEY + assert_not SERVICE.primary.exist?(FIXTURE_KEY) + SERVICE.mirrors.each do |mirror| + assert_not mirror.exist?(FIXTURE_KEY) + end + end + + test "URL generation in primary service" do + filename = ActiveStorage::Filename.new("test.txt") + + freeze_time do + assert_equal SERVICE.primary.url(FIXTURE_KEY, expires_in: 2.minutes, disposition: :inline, filename: filename, content_type: "text/plain"), + @service.url(FIXTURE_KEY, expires_in: 2.minutes, disposition: :inline, filename: filename, content_type: "text/plain") + end + end + + private + def upload(data, to:) + SecureRandom.base58(24).tap do |key| + io = StringIO.new(data).tap(&:read) + @service.upload key, io, checksum: Digest::MD5.base64digest(data) + assert io.eof? + 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..c3818422aa --- /dev/null +++ b/activestorage/test/service/s3_service_test.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +require "service/shared_service_tests" +require "net/http" + +if SERVICE_CONFIGURATIONS[:s3] && SERVICE_CONFIGURATIONS[:s3][:access_key_id].present? + 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 "signed URL generation" do + url = @service.url(FIXTURE_KEY, expires_in: 5.minutes, + disposition: :inline, filename: ActiveStorage::Filename.new("avatar.png"), content_type: "image/png") + + assert_match(/s3\.(\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..ce28c4393a --- /dev/null +++ b/activestorage/test/service/shared_service_tests.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true + +require "test_helper" +require "active_support/core_ext/securerandom" + +module ActiveStorage::Service::SharedServiceTests + extend ActiveSupport::Concern + + FIXTURE_KEY = SecureRandom.base58(24) + 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 + @service = self.class.const_get(:SERVICE) + @service.upload FIXTURE_KEY, StringIO.new(FIXTURE_DATA) + end + + teardown do + @service.delete FIXTURE_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(FIXTURE_KEY) + end + + test "downloading in chunks" do + chunks = [] + + @service.download(FIXTURE_KEY) do |chunk| + chunks << chunk + end + + assert_equal [ FIXTURE_DATA ], chunks + end + + test "existing" do + assert @service.exist?(FIXTURE_KEY) + assert_not @service.exist?(FIXTURE_KEY + "nonsense") + end + + test "deleting" do + @service.delete FIXTURE_KEY + assert_not @service.exist?(FIXTURE_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 diff --git a/activestorage/test/template/image_tag_test.rb b/activestorage/test/template/image_tag_test.rb new file mode 100644 index 0000000000..80f183e0e3 --- /dev/null +++ b/activestorage/test/template/image_tag_test.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require "test_helper" +require "database/setup" + +class ActiveStorage::ImageTagTest < ActionView::TestCase + tests ActionView::Helpers::AssetTagHelper + + setup do + @blob = create_file_blob filename: "racecar.jpg" + end + + test "blob" do + assert_dom_equal %(<img src="#{polymorphic_url @blob}" />), image_tag(@blob) + end + + test "variant" do + variant = @blob.variant(resize: "100x100") + assert_dom_equal %(<img src="#{polymorphic_url variant}" />), image_tag(variant) + end + + test "preview" do + blob = create_file_blob(filename: "report.pdf", content_type: "application/pdf") + preview = blob.preview(resize: "100x100") + assert_dom_equal %(<img src="#{polymorphic_url preview}" />), image_tag(preview) + end + + test "attachment" do + attachment = ActiveStorage::Attachment.new(blob: @blob) + assert_dom_equal %(<img src="#{polymorphic_url attachment}" />), image_tag(attachment) + end + + test "error when attachment's empty" do + @user = User.create!(name: "DHH") + + assert_not @user.avatar.attached? + assert_raises(ArgumentError) { image_tag(@user.avatar) } + end + + test "error when object can't be resolved into url" do + unresolvable_object = ActionView::Helpers::AssetTagHelper + assert_raises(ArgumentError) { image_tag(unresolvable_object) } + end +end diff --git a/activestorage/test/test_helper.rb b/activestorage/test/test_helper.rb new file mode 100644 index 0000000000..aaf1d452ea --- /dev/null +++ b/activestorage/test/test_helper.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +ENV["RAILS_ENV"] ||= "test" +require_relative "dummy/config/environment.rb" + +require "bundler/setup" +require "active_support" +require "active_support/test_case" +require "active_support/testing/autorun" +require "mini_magick" + +begin + require "byebug" +rescue LoadError +end + +require "active_job" +ActiveJob::Base.queue_adapter = :test +ActiveJob::Base.logger = ActiveSupport::Logger.new(nil) + +# Filter out Minitest backtrace while allowing backtrace from other libraries +# to be shown. +Minitest.backtrace_filter = Minitest::BacktraceFilter.new + +require "yaml" +SERVICE_CONFIGURATIONS = begin + erb = ERB.new(Pathname.new(File.expand_path("service/configurations.yml", __dir__)).read) + configuration = YAML.load(erb.result) || {} + configuration.deep_symbolize_keys +rescue Errno::ENOENT + puts "Missing service configuration file in test/service/configurations.yml" + {} +end + +require "tmpdir" +ActiveStorage::Blob.service = ActiveStorage::Service::DiskService.new(root: Dir.mktmpdir("active_storage_tests")) + +ActiveStorage.logger = ActiveSupport::Logger.new(nil) +ActiveStorage.verifier = ActiveSupport::MessageVerifier.new("Testing") + +class ActiveSupport::TestCase + self.file_fixture_path = File.expand_path("fixtures/files", __dir__) + + private + def create_blob(data: "Hello world!", filename: "hello.txt", content_type: "text/plain") + ActiveStorage::Blob.create_after_upload! io: StringIO.new(data), filename: filename, content_type: content_type + end + + def create_file_blob(filename: "racecar.jpg", content_type: "image/jpeg", metadata: nil) + ActiveStorage::Blob.create_after_upload! io: file_fixture(filename).open, filename: filename, content_type: content_type, metadata: metadata + end + + def create_blob_before_direct_upload(filename: "hello.txt", byte_size:, checksum:, content_type: "text/plain") + ActiveStorage::Blob.create_before_direct_upload! filename: filename, byte_size: byte_size, checksum: checksum, content_type: content_type + end + + def read_image(blob_or_variant) + MiniMagick::Image.open blob_or_variant.service.send(:path_for, blob_or_variant.key) + end +end + +require "global_id" +GlobalID.app = "ActiveStorageExampleApp" +ActiveRecord::Base.send :include, GlobalID::Identification + +class User < ActiveRecord::Base + has_one_attached :avatar + has_many_attached :highlights +end |