aboutsummaryrefslogtreecommitdiffstats
path: root/activestorage/test/models
diff options
context:
space:
mode:
Diffstat (limited to 'activestorage/test/models')
-rw-r--r--activestorage/test/models/attachments_test.rb370
-rw-r--r--activestorage/test/models/blob_test.rb145
-rw-r--r--activestorage/test/models/filename/parameters_test.rb32
-rw-r--r--activestorage/test/models/filename_test.rb56
-rw-r--r--activestorage/test/models/preview_test.rb40
-rw-r--r--activestorage/test/models/representation_test.rb41
-rw-r--r--activestorage/test/models/variant_test.rb83
7 files changed, 767 insertions, 0 deletions
diff --git a/activestorage/test/models/attachments_test.rb b/activestorage/test/models/attachments_test.rb
new file mode 100644
index 0000000000..9ba2759893
--- /dev/null
+++ b/activestorage/test/models/attachments_test.rb
@@ -0,0 +1,370 @@
+# 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_predicate user.avatar, :attached?
+ assert_equal "funky.jpg", user.avatar.filename.to_s
+
+ assert_difference -> { ActiveStorage::Attachment.count }, +1 do
+ user.save!
+ end
+
+ assert_predicate 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_predicate @user, :new_record?
+ assert_predicate @user.avatar, :attached?
+ assert_equal "town.jpg", @user.avatar.filename.to_s
+
+ @user.save!
+ assert_predicate @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 "identify newly-attached, directly-uploaded blob" do
+ # Simulate a direct upload.
+ blob = create_blob_before_direct_upload(filename: "racecar.jpg", content_type: "application/octet-stream", byte_size: 1124062, checksum: "7GjDDNEQb4mzMzsW+MS0JQ==")
+ ActiveStorage::Blob.service.upload(blob.key, file_fixture("racecar.jpg").open)
+
+ stub_request(:get, %r{localhost:3000/rails/active_storage/disk/.*}).to_return(body: file_fixture("racecar.jpg"))
+ @user.avatar.attach(blob)
+
+ assert_equal "image/jpeg", @user.avatar.reload.content_type
+ assert_predicate @user.avatar, :identified?
+ end
+
+ test "identify newly-attached blob only once" do
+ blob = create_file_blob
+ assert_predicate blob, :identified?
+
+ # The blob's backing file is a PNG image. Fudge its content type so we can tell if it's identified when we attach it.
+ blob.update! content_type: "application/octet-stream"
+
+ @user.avatar.attach blob
+ assert_equal "application/octet-stream", blob.content_type
+ 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_predicate blob.reload, :analyzed?
+
+ @user.avatar.detach
+
+ 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_predicate @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_predicate @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_predicate 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_predicate 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_predicate @user, :new_record?
+ assert_predicate @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_predicate @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_predicate @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_predicate @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..779e47ffb6
--- /dev/null
+++ b/activestorage/test/models/blob_test.rb
@@ -0,0 +1,145 @@
+# frozen_string_literal: true
+
+require "test_helper"
+require "database/setup"
+require "active_support/testing/method_call_assertions"
+
+class ActiveStorage::BlobTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::MethodCallAssertions
+
+ test ".unattached scope returns not attached blobs" do
+ class UserWithHasOneAttachedDependentFalse < User
+ has_one_attached :avatar, dependent: false
+ end
+
+ ActiveStorage::Blob.delete_all
+ blob_1 = create_blob filename: "funky.jpg"
+ blob_2 = create_blob filename: "town.jpg"
+
+ user = UserWithHasOneAttachedDependentFalse.create!
+ user.avatar.attach blob_1
+
+ assert_equal [blob_2], ActiveStorage::Blob.unattached
+ user.destroy
+ assert_equal [blob_1, blob_2].map(&:id).sort, ActiveStorage::Blob.unattached.pluck(:id).sort
+ end
+
+ 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 "create after upload extracts content type from data" do
+ blob = create_file_blob content_type: "application/octet-stream"
+ assert_equal "image/jpeg", blob.content_type
+ end
+
+ test "create after upload extracts content type from filename" do
+ blob = create_blob content_type: "application/octet-stream"
+ assert_equal "text/plain", blob.content_type
+ end
+
+ test "image?" do
+ blob = create_file_blob filename: "racecar.jpg"
+ assert_predicate blob, :image?
+ assert_not_predicate blob, :audio?
+ end
+
+ test "video?" do
+ blob = create_file_blob(filename: "video.mp4", content_type: "video/mp4")
+ assert_predicate blob, :video?
+ assert_not_predicate blob, :audio?
+ end
+
+ test "text?" do
+ blob = create_blob data: "Hello world!"
+ assert_predicate blob, :text?
+ assert_not_predicate 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)
+ assert_equal expected_url_for(blob, filename: new_filename), blob.service_url(filename: "new.txt")
+ assert_equal expected_url_for(blob, filename: blob.filename), blob.service_url(filename: nil)
+ end
+ end
+
+ test "urls allow for custom options" do
+ blob = create_blob(filename: "original.txt")
+
+ options = [
+ blob.key,
+ expires_in: blob.service.url_expires_in,
+ disposition: :inline,
+ content_type: blob.content_type,
+ filename: blob.filename,
+ thumb_size: "300x300",
+ thumb_mode: "crop"
+ ]
+ assert_called_with(blob.service, :url, options) do
+ blob.service_url(thumb_size: "300x300", thumb_mode: "crop")
+ 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
+ "http://localhost:3000/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..55fdc228c8
--- /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_predicate 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_predicate 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..0f3ada25c0
--- /dev/null
+++ b/activestorage/test/models/variant_test.rb
@@ -0,0 +1,83 @@
+# 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 "resized variation of ICO blob" do
+ blob = create_file_blob(filename: "favicon.ico", content_type: "image/vnd.microsoft.icon")
+ 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, :<, 525
+ end
+end