mirror of
https://github.com/discourse/discourse.git
synced 2024-11-24 04:13:22 +08:00
d0243f741e
We previously had a system which would generate a 10x10px preview of images and add their URLs in a data-small-upload attribute. The client would then use that as the background-image of the `<img>` element. This works reasonably well on fast connections, but on slower connections it can take a few seconds for the placeholders to appear. The act of loading the placeholders can also break or delay the loading of the 'real' images. This commit replaces the placeholder logic with a new approach. Instead of a 10x10px preview, we use imagemagick to calculate the average color of an image and store it in the database. The hex color value then added as a `data-dominant-color` attribute on the `<img>` element, and the client can use this as a `background-color` on the element while the real image is loading. That means no extra HTTP request is required, and so the placeholder color can appear instantly. Dominant color will be calculated: 1. When a new upload is created 2. During a post rebake, if the dominant color is missing from an upload, it will be calculated and stored 3. Every 15 minutes, 25 old upload records are fetched and their dominant color calculated and stored. (part of the existing PeriodicalUpdates job) Existing posts will continue to use the old 10x10px placeholder system until they are next rebaked
694 lines
24 KiB
Ruby
694 lines
24 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
RSpec.describe Upload do
|
|
let(:upload) { build(:upload) }
|
|
let(:user_id) { 1 }
|
|
|
|
let(:image_filename) { "logo.png" }
|
|
let(:image) { file_from_fixtures(image_filename) }
|
|
|
|
let(:image_svg_filename) { "image.svg" }
|
|
let(:image_svg) { file_from_fixtures(image_svg_filename) }
|
|
|
|
let(:huge_image_filename) { "huge.jpg" }
|
|
let(:huge_image) { file_from_fixtures(huge_image_filename) }
|
|
|
|
let(:attachment_path) { __FILE__ }
|
|
let(:attachment) { File.new(attachment_path) }
|
|
|
|
describe '.with_no_non_post_relations' do
|
|
it "does not find non-post related uploads" do
|
|
post_upload = Fabricate(:upload)
|
|
post = Fabricate(:post, raw: "<img src='#{post_upload.url}'>")
|
|
post.link_post_uploads
|
|
|
|
badge_upload = Fabricate(:upload)
|
|
Fabricate(:badge, image_upload: badge_upload)
|
|
|
|
avatar_upload = Fabricate(:upload)
|
|
Fabricate(:user, uploaded_avatar: avatar_upload)
|
|
|
|
site_setting_upload = Fabricate(:upload)
|
|
SiteSetting.create!(
|
|
name: "logo",
|
|
data_type: SiteSettings::TypeSupervisor.types[:upload],
|
|
value: site_setting_upload.id
|
|
)
|
|
|
|
upload_ids = Upload
|
|
.by_users
|
|
.with_no_non_post_relations
|
|
.pluck(:id)
|
|
expect(upload_ids).to eq([post_upload.id])
|
|
end
|
|
end
|
|
|
|
describe ".create_thumbnail!" do
|
|
it "does not create a thumbnail when disabled" do
|
|
SiteSetting.create_thumbnails = false
|
|
OptimizedImage.expects(:create_for).never
|
|
upload.create_thumbnail!(100, 100)
|
|
end
|
|
|
|
it "creates a thumbnail" do
|
|
upload = Fabricate(:upload)
|
|
thumbnail = Fabricate(:optimized_image, upload: upload)
|
|
SiteSetting.expects(:create_thumbnails?).returns(true)
|
|
OptimizedImage.expects(:create_for).returns(thumbnail)
|
|
upload.create_thumbnail!(100, 100)
|
|
upload.reload
|
|
expect(upload.optimized_images.count).to eq(1)
|
|
end
|
|
end
|
|
|
|
it "supports <style> element in SVG" do
|
|
SiteSetting.authorized_extensions = "svg"
|
|
|
|
upload = UploadCreator.new(image_svg, image_svg_filename).create_for(user_id)
|
|
expect(upload.valid?).to eq(true)
|
|
|
|
path = Discourse.store.path_for(upload)
|
|
expect(File.read(path)).to match(/<style>/)
|
|
end
|
|
|
|
it "can reconstruct dimensions on demand" do
|
|
upload = UploadCreator.new(huge_image, "image.png").create_for(user_id)
|
|
|
|
upload.update_columns(width: nil, height: nil, thumbnail_width: nil, thumbnail_height: nil)
|
|
|
|
upload = Upload.find(upload.id)
|
|
|
|
expect(upload.width).to eq(8900)
|
|
expect(upload.height).to eq(8900)
|
|
|
|
upload.reload
|
|
expect(upload.read_attribute(:width)).to eq(8900)
|
|
|
|
upload.update_columns(width: nil, height: nil, thumbnail_width: nil, thumbnail_height: nil)
|
|
|
|
expect(upload.thumbnail_width).to eq(500)
|
|
expect(upload.thumbnail_height).to eq(500)
|
|
end
|
|
|
|
it "dimension calculation returns nil on missing image" do
|
|
upload = UploadCreator.new(huge_image, "image.png").create_for(user_id)
|
|
upload.update_columns(width: nil, height: nil, thumbnail_width: nil, thumbnail_height: nil)
|
|
|
|
missing_url = "wrong_folder#{upload.url}"
|
|
upload.update_columns(url: missing_url)
|
|
expect(upload.thumbnail_height).to eq(nil)
|
|
expect(upload.thumbnail_width).to eq(nil)
|
|
end
|
|
|
|
it 'returns error when image resolution is to big' do
|
|
SiteSetting.max_image_megapixels = 10
|
|
upload = UploadCreator.new(huge_image, "image.png").create_for(user_id)
|
|
expect(upload.persisted?).to eq(false)
|
|
expect(upload.errors.messages[:base].first).to eq(I18n.t("upload.images.larger_than_x_megapixels", max_image_megapixels: 20))
|
|
end
|
|
|
|
it "extracts file extension" do
|
|
created_upload = UploadCreator.new(image, image_filename).create_for(user_id)
|
|
expect(created_upload.extension).to eq("png")
|
|
end
|
|
|
|
it "should create an invalid upload when the filename is blank" do
|
|
SiteSetting.authorized_extensions = "*"
|
|
created_upload = UploadCreator.new(attachment, nil).create_for(user_id)
|
|
expect(created_upload.valid?).to eq(false)
|
|
end
|
|
|
|
describe ".extract_url" do
|
|
let(:url) { 'https://example.com/uploads/default/original/1X/d1c2d40ab994e8410c.png' }
|
|
|
|
it 'should return the right part of url' do
|
|
expect(Upload.extract_url(url).to_s).to eq('/original/1X/d1c2d40ab994e8410c.png')
|
|
end
|
|
end
|
|
|
|
describe ".get_from_url" do
|
|
let(:sha1) { "10f73034616a796dfd70177dc54b6def44c4ba6f" }
|
|
let(:upload) { Fabricate(:upload, sha1: sha1) }
|
|
|
|
it "works when the file has been uploaded" do
|
|
expect(Upload.get_from_url(upload.url)).to eq(upload)
|
|
end
|
|
|
|
describe 'for an extensionless url' do
|
|
before do
|
|
upload.update!(url: upload.url.sub('.png', ''))
|
|
upload.reload
|
|
end
|
|
|
|
it 'should return the right upload' do
|
|
expect(Upload.get_from_url(upload.url)).to eq(upload)
|
|
end
|
|
end
|
|
|
|
it "should return the right upload as long as the upload's URL matches" do
|
|
upload.update!(url: "/uploads/default/12345/971308e535305c51.png")
|
|
|
|
expect(Upload.get_from_url(upload.url)).to eq(upload)
|
|
|
|
expect(Upload.get_from_url("/uploads/default/123131/971308e535305c51.png"))
|
|
.to eq(nil)
|
|
end
|
|
|
|
describe 'for a url a tree' do
|
|
before do
|
|
upload.update!(url:
|
|
Discourse.store.get_path_for(
|
|
"original",
|
|
16001,
|
|
upload.sha1,
|
|
".#{upload.extension}"
|
|
)
|
|
)
|
|
end
|
|
|
|
it 'should return the right upload' do
|
|
expect(Upload.get_from_url(upload.url)).to eq(upload)
|
|
end
|
|
end
|
|
|
|
it "works when using a cdn" do
|
|
begin
|
|
original_asset_host = Rails.configuration.action_controller.asset_host
|
|
Rails.configuration.action_controller.asset_host = 'http://my.cdn.com'
|
|
|
|
expect(Upload.get_from_url(
|
|
URI.join("http://my.cdn.com", upload.url).to_s
|
|
)).to eq(upload)
|
|
ensure
|
|
Rails.configuration.action_controller.asset_host = original_asset_host
|
|
end
|
|
end
|
|
|
|
it "should return the right upload when using the full URL" do
|
|
expect(Upload.get_from_url(
|
|
URI.join("http://discourse.some.com:3000/", upload.url).to_s
|
|
)).to eq(upload)
|
|
end
|
|
|
|
it "doesn't blow up with an invalid URI" do
|
|
expect { Upload.get_from_url("http://ip:port/index.html") }.not_to raise_error
|
|
expect { Upload.get_from_url("mailto:admin%40example.com") }.not_to raise_error
|
|
expect { Upload.get_from_url("mailto:example") }.not_to raise_error
|
|
end
|
|
|
|
describe "s3 store" do
|
|
let(:upload) { Fabricate(:upload_s3) }
|
|
let(:path) { upload.url.sub(SiteSetting.Upload.s3_base_url, '') }
|
|
|
|
before do
|
|
setup_s3
|
|
end
|
|
|
|
it "can download an s3 upload" do
|
|
stub_request(:get, upload.url)
|
|
.to_return(status: 200, body: "hello", headers: {})
|
|
|
|
expect(upload.content).to eq("hello")
|
|
end
|
|
|
|
it "should return the right upload when using base url (not CDN) for s3" do
|
|
upload
|
|
expect(Upload.get_from_url(upload.url)).to eq(upload)
|
|
end
|
|
|
|
describe 'when using a cdn' do
|
|
let(:s3_cdn_url) { 'https://mycdn.slowly.net' }
|
|
|
|
before do
|
|
SiteSetting.s3_cdn_url = s3_cdn_url
|
|
end
|
|
|
|
it "should return the right upload" do
|
|
upload
|
|
expect(Upload.get_from_url(URI.join(s3_cdn_url, path).to_s)).to eq(upload)
|
|
end
|
|
|
|
describe 'when upload bucket contains subfolder' do
|
|
before do
|
|
SiteSetting.s3_upload_bucket = "s3-upload-bucket/path/path2"
|
|
end
|
|
|
|
it "should return the right upload" do
|
|
upload
|
|
expect(Upload.get_from_url(URI.join(s3_cdn_url, path).to_s)).to eq(upload)
|
|
end
|
|
end
|
|
end
|
|
|
|
it "should return the right upload when using one CDN for both s3 and assets" do
|
|
begin
|
|
original_asset_host = Rails.configuration.action_controller.asset_host
|
|
cdn_url = 'http://my.cdn.com'
|
|
Rails.configuration.action_controller.asset_host = cdn_url
|
|
SiteSetting.s3_cdn_url = cdn_url
|
|
upload
|
|
|
|
expect(Upload.get_from_url(
|
|
URI.join(cdn_url, path).to_s
|
|
)).to eq(upload)
|
|
ensure
|
|
Rails.configuration.action_controller.asset_host = original_asset_host
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe ".get_from_urls" do
|
|
let(:upload) { Fabricate(:upload, sha1: "10f73034616a796dfd70177dc54b6def44c4ba6f") }
|
|
let(:upload2) { Fabricate(:upload, sha1: "2a7081e615f9075befd87a9a6d273935c0262cd5") }
|
|
|
|
it "works with multiple uploads" do
|
|
expect(Upload.get_from_urls([upload.url, upload2.url])).to contain_exactly(upload, upload2)
|
|
end
|
|
|
|
it "works for an extensionless URL" do
|
|
url = upload.url.sub('.png', '')
|
|
upload.update!(url: url)
|
|
expect(Upload.get_from_urls([url])).to contain_exactly(upload)
|
|
end
|
|
|
|
it "works with uploads with mismatched URLs" do
|
|
upload.update!(url: "/uploads/default/12345/971308e535305c51.png")
|
|
expect(Upload.get_from_urls([upload.url])).to contain_exactly(upload)
|
|
expect(Upload.get_from_urls(["/uploads/default/123131/971308e535305c51.png"])).to be_empty
|
|
end
|
|
|
|
it "works with an upload with a URL containing a deep tree" do
|
|
upload.update!(url: Discourse.store.get_path_for("original", 16001, upload.sha1, ".#{upload.extension}"))
|
|
expect(Upload.get_from_urls([upload.url])).to contain_exactly(upload)
|
|
end
|
|
|
|
it "works when using a CDN" do
|
|
begin
|
|
original_asset_host = Rails.configuration.action_controller.asset_host
|
|
Rails.configuration.action_controller.asset_host = 'http://my.cdn.com'
|
|
|
|
expect(Upload.get_from_urls([
|
|
URI.join("http://my.cdn.com", upload.url).to_s
|
|
])).to contain_exactly(upload)
|
|
ensure
|
|
Rails.configuration.action_controller.asset_host = original_asset_host
|
|
end
|
|
end
|
|
|
|
it "works with full URLs" do
|
|
expect(Upload.get_from_urls([
|
|
URI.join("http://discourse.some.com:3000/", upload.url).to_s
|
|
])).to contain_exactly(upload)
|
|
end
|
|
|
|
it "handles invalid URIs" do
|
|
urls = ["http://ip:port/index.html", "mailto:admin%40example.com", "mailto:example"]
|
|
expect { Upload.get_from_urls(urls) }.not_to raise_error
|
|
end
|
|
end
|
|
|
|
describe '.generate_digest' do
|
|
it "should return the right digest" do
|
|
expect(Upload.generate_digest(image.path)).to eq('bc975735dfc6409c1c2aa5ebf2239949bcbdbd65')
|
|
end
|
|
end
|
|
|
|
describe '.short_url' do
|
|
it "should generate a correct short url" do
|
|
upload = Upload.new(sha1: 'bda2c513e1da04f7b4e99230851ea2aafeb8cc4e', extension: 'png')
|
|
expect(upload.short_url).to eq('upload://r3AYqESanERjladb4vBB7VsMBm6.png')
|
|
|
|
upload.extension = nil
|
|
expect(upload.short_url).to eq('upload://r3AYqESanERjladb4vBB7VsMBm6')
|
|
end
|
|
end
|
|
|
|
describe '.sha1_from_short_url' do
|
|
it "should be able to look up sha1" do
|
|
sha1 = 'bda2c513e1da04f7b4e99230851ea2aafeb8cc4e'
|
|
|
|
expect(Upload.sha1_from_short_url('upload://r3AYqESanERjladb4vBB7VsMBm6.png')).to eq(sha1)
|
|
expect(Upload.sha1_from_short_url('upload://r3AYqESanERjladb4vBB7VsMBm6')).to eq(sha1)
|
|
expect(Upload.sha1_from_short_url('r3AYqESanERjladb4vBB7VsMBm6')).to eq(sha1)
|
|
end
|
|
|
|
it "should be able to look up sha1 even with leading zeros" do
|
|
sha1 = '0000c513e1da04f7b4e99230851ea2aafeb8cc4e'
|
|
expect(Upload.sha1_from_short_url('upload://1Eg9p8rrCURq4T3a6iJUk0ri6.png')).to eq(sha1)
|
|
end
|
|
end
|
|
|
|
describe '#base62_sha1' do
|
|
it 'should return the right value' do
|
|
upload.update!(sha1: "0000c513e1da04f7b4e99230851ea2aafeb8cc4e")
|
|
expect(upload.base62_sha1).to eq("1Eg9p8rrCURq4T3a6iJUk0ri6")
|
|
end
|
|
end
|
|
|
|
describe '.sha1_from_short_path' do
|
|
it "should be able to lookup sha1" do
|
|
path = "/uploads/short-url/3UjQ4jHoyeoQndk5y3qHzm3QVTQ.png"
|
|
sha1 = "1b6453892473a467d07372d45eb05abc2031647a"
|
|
|
|
expect(Upload.sha1_from_short_path(path)).to eq(sha1)
|
|
expect(Upload.sha1_from_short_path(path.sub(".png", ""))).to eq(sha1)
|
|
end
|
|
end
|
|
|
|
describe '#to_s' do
|
|
it 'should return the right value' do
|
|
expect(upload.to_s).to eq(upload.url)
|
|
end
|
|
end
|
|
|
|
describe '.migrate_to_new_scheme' do
|
|
it 'should not migrate system uploads' do
|
|
SiteSetting.migrate_to_new_scheme = true
|
|
|
|
expect { Upload.migrate_to_new_scheme }
|
|
.to_not change { Upload.pluck(:url) }
|
|
end
|
|
end
|
|
|
|
describe '.update_secure_status' do
|
|
it "respects the override parameter if provided" do
|
|
upload.update!(secure: true)
|
|
|
|
upload.update_secure_status(override: true)
|
|
|
|
expect(upload.secure).to eq(true)
|
|
|
|
upload.update_secure_status(override: false)
|
|
|
|
expect(upload.secure).to eq(false)
|
|
end
|
|
|
|
it 'marks a local upload as not secure with default settings' do
|
|
upload.update!(secure: true)
|
|
expect { upload.update_secure_status }
|
|
.to change { upload.secure }
|
|
|
|
expect(upload.secure).to eq(false)
|
|
end
|
|
|
|
context "with local attachment" do
|
|
before do
|
|
SiteSetting.authorized_extensions = "pdf"
|
|
end
|
|
|
|
let(:upload) { Fabricate(:upload, original_filename: "small.pdf", extension: "pdf", secure: true) }
|
|
|
|
it 'marks a local attachment as secure if secure media enabled' do
|
|
upload.update!(secure: false, access_control_post: Fabricate(:private_message_post))
|
|
enable_secure_media
|
|
|
|
expect { upload.update_secure_status }
|
|
.to change { upload.secure }
|
|
|
|
expect(upload.secure).to eq(true)
|
|
end
|
|
|
|
it 'marks a local attachment as not secure if secure media enabled' do
|
|
expect { upload.update_secure_status }
|
|
.to change { upload.secure }
|
|
|
|
expect(upload.secure).to eq(false)
|
|
end
|
|
end
|
|
|
|
it 'does not change secure status of a non-attachment when prevent_anons_from_downloading_files is enabled by itself' do
|
|
SiteSetting.prevent_anons_from_downloading_files = true
|
|
SiteSetting.authorized_extensions = "mp4"
|
|
upload.update!(original_filename: "small.mp4", extension: "mp4")
|
|
|
|
expect { upload.update_secure_status }
|
|
.not_to change { upload.secure }
|
|
|
|
expect(upload.secure).to eq(false)
|
|
end
|
|
|
|
context "with secure media enabled" do
|
|
before do
|
|
enable_secure_media
|
|
end
|
|
|
|
it "does not mark an image upload as not secure when there is no access control post id, to avoid unintentional exposure" do
|
|
upload.update!(secure: true)
|
|
upload.update_secure_status
|
|
expect(upload.secure).to eq(true)
|
|
end
|
|
|
|
it "marks the upload as not secure if its access control post is a public post" do
|
|
upload.update!(secure: true, access_control_post: Fabricate(:post))
|
|
upload.update_secure_status
|
|
expect(upload.secure).to eq(false)
|
|
end
|
|
|
|
it "leaves the upload as secure if its access control post is a PM post" do
|
|
upload.update!(secure: true, access_control_post: Fabricate(:private_message_post))
|
|
upload.update_secure_status
|
|
expect(upload.secure).to eq(true)
|
|
end
|
|
|
|
it "marks an image upload as secure if login_required is enabled" do
|
|
SiteSetting.login_required = true
|
|
upload.update!(secure: false)
|
|
|
|
expect { upload.update_secure_status }
|
|
.to change { upload.secure }
|
|
|
|
expect(upload.reload.secure).to eq(true)
|
|
end
|
|
|
|
it "does not mark an upload used for a custom emoji as secure" do
|
|
SiteSetting.login_required = true
|
|
upload.update!(secure: false)
|
|
CustomEmoji.create(name: "meme", upload: upload)
|
|
upload.update_secure_status
|
|
expect(upload.reload.secure).to eq(false)
|
|
end
|
|
|
|
it "does not mark an upload whose origin matches a regular emoji as secure (sometimes emojis are downloaded in pull_hotlinked_images)" do
|
|
SiteSetting.login_required = true
|
|
falafel = Emoji.all.find { |e| e.url == "/images/emoji/twitter/falafel.png?v=#{Emoji::EMOJI_VERSION}" }
|
|
upload.update!(secure: false, origin: "http://localhost:3000#{falafel.url}")
|
|
upload.update_secure_status
|
|
expect(upload.reload.secure).to eq(false)
|
|
end
|
|
|
|
it "does not mark any upload with origin containing images/emoji in the URL" do
|
|
SiteSetting.login_required = true
|
|
upload.update!(secure: false, origin: "http://localhost:3000/images/emoji/test.png")
|
|
upload.update_secure_status
|
|
expect(upload.reload.secure).to eq(false)
|
|
end
|
|
|
|
it "does not throw an error if the object storage provider does not support ACLs" do
|
|
FileStore::S3Store.any_instance.stubs(:update_upload_ACL).raises(
|
|
Aws::S3::Errors::NotImplemented.new("A header you provided implies functionality that is not implemented", "")
|
|
)
|
|
upload.update!(secure: true, access_control_post: Fabricate(:private_message_post))
|
|
expect { upload.update_secure_status }.not_to raise_error
|
|
end
|
|
|
|
it "succeeds even if the extension of the upload is not authorized" do
|
|
upload.update!(secure: false, access_control_post: Fabricate(:private_message_post))
|
|
SiteSetting.login_required = true
|
|
SiteSetting.authorized_extensions = ""
|
|
upload.update_secure_status
|
|
upload.reload
|
|
expect(upload.secure).to eq(true)
|
|
end
|
|
|
|
it "respects the authorized extensions when creating a new upload, no matter its secure status" do
|
|
SiteSetting.login_required = true
|
|
SiteSetting.authorized_extensions = ""
|
|
expect do
|
|
upl = Fabricate(
|
|
:upload,
|
|
access_control_post: Fabricate(:private_message_post),
|
|
security_last_changed_at: Time.zone.now,
|
|
security_last_changed_reason: "test",
|
|
secure: true
|
|
)
|
|
end.to raise_error(ActiveRecord::RecordInvalid)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '.extract_upload_ids' do
|
|
let(:upload) { Fabricate(:upload) }
|
|
|
|
it 'works with short URLs' do
|
|
ids = Upload.extract_upload_ids("This URL #{upload.short_url} is an upload")
|
|
expect(ids).to contain_exactly(upload.id)
|
|
end
|
|
|
|
it 'works with SHA1s' do
|
|
ids = Upload.extract_upload_ids("This URL /#{upload.sha1} is an upload")
|
|
expect(ids).to contain_exactly(upload.id)
|
|
end
|
|
|
|
it 'works with Base62 hashes' do
|
|
ids = Upload.extract_upload_ids("This URL /#{Upload.base62_sha1(upload.sha1)} is an upload")
|
|
expect(ids).to contain_exactly(upload.id)
|
|
end
|
|
|
|
it 'works with shorter base62 hashes (when sha1 has leading 0s)' do
|
|
upload.update(sha1: "0000c513e1da04f7b4e99230851ea2aafeb8cc4e")
|
|
base62 = Upload.base62_sha1(upload.sha1).delete_prefix("0")
|
|
ids = Upload.extract_upload_ids("This URL /#{base62} is an upload")
|
|
expect(ids).to contain_exactly(upload.id)
|
|
end
|
|
end
|
|
|
|
def enable_secure_media
|
|
setup_s3
|
|
SiteSetting.secure_media = true
|
|
stub_upload(upload)
|
|
end
|
|
|
|
describe '.destroy' do
|
|
it "can correctly clear information when destroying an upload" do
|
|
upload = Fabricate(:upload)
|
|
user = Fabricate(:user)
|
|
|
|
user.user_profile.update!(
|
|
card_background_upload_id: upload.id,
|
|
profile_background_upload_id: upload.id
|
|
)
|
|
|
|
upload.destroy
|
|
|
|
user.user_profile.reload
|
|
|
|
expect(user.user_profile.card_background_upload_id).to eq(nil)
|
|
expect(user.user_profile.profile_background_upload_id).to eq(nil)
|
|
end
|
|
end
|
|
|
|
describe ".secure_media_url_from_upload_url" do
|
|
before do
|
|
# must be done so signed_url_for_path exists
|
|
enable_secure_media
|
|
end
|
|
|
|
it "gets the secure media url from an S3 upload url" do
|
|
upload = Fabricate(:upload_s3, secure: true)
|
|
url = upload.url
|
|
secure_url = Upload.secure_media_url_from_upload_url(url)
|
|
expect(secure_url).not_to include(SiteSetting.Upload.absolute_base_url)
|
|
end
|
|
end
|
|
|
|
describe ".secure_media_url?" do
|
|
it "works for a secure media url with or without schema + host" do
|
|
url = "//localhost:3000/secure-media-uploads/original/2X/f/f62055931bb702c7fd8f552fb901f977e0289a18.png"
|
|
expect(Upload.secure_media_url?(url)).to eq(true)
|
|
url = "/secure-media-uploads/original/2X/f/f62055931bb702c7fd8f552fb901f977e0289a18.png"
|
|
expect(Upload.secure_media_url?(url)).to eq(true)
|
|
url = "http://localhost:3000/secure-media-uploads/original/2X/f/f62055931bb702c7fd8f552fb901f977e0289a18.png"
|
|
expect(Upload.secure_media_url?(url)).to eq(true)
|
|
end
|
|
|
|
it "does not get false positives on a topic url" do
|
|
url = "/t/secure-media-uploads-are-cool/42839"
|
|
expect(Upload.secure_media_url?(url)).to eq(false)
|
|
end
|
|
|
|
it "returns true only for secure media URL for actual media (images/video/audio)" do
|
|
url = "/secure-media-uploads/original/2X/f/f62055931bb702c7fd8f552fb901f977e0289a18.mp4"
|
|
expect(Upload.secure_media_url?(url)).to eq(true)
|
|
url = "/secure-media-uploads/original/2X/f/f62055931bb702c7fd8f552fb901f977e0289a18.png"
|
|
expect(Upload.secure_media_url?(url)).to eq(true)
|
|
url = "/secure-media-uploads/original/2X/f/f62055931bb702c7fd8f552fb901f977e0289a18.mp3"
|
|
expect(Upload.secure_media_url?(url)).to eq(true)
|
|
url = "/secure-media-uploads/original/2X/f/f62055931bb702c7fd8f552fb901f977e0289a18.pdf"
|
|
expect(Upload.secure_media_url?(url)).to eq(false)
|
|
end
|
|
|
|
it "does not work for regular upload urls" do
|
|
url = "/uploads/default/test_0/original/1X/e1864389d8252958586c76d747b069e9f68827e3.png"
|
|
expect(Upload.secure_media_url?(url)).to eq(false)
|
|
end
|
|
|
|
it "does not raise for invalid URLs" do
|
|
url = "http://URL:%20https://google.com"
|
|
expect(Upload.secure_media_url?(url)).to eq(false)
|
|
end
|
|
end
|
|
|
|
describe "#dominant_color" do
|
|
let(:white_image) { Fabricate(:image_upload, color: "white") }
|
|
let(:red_image) { Fabricate(:image_upload, color: "red") }
|
|
let(:not_an_image) {
|
|
upload = Fabricate(:upload)
|
|
|
|
file = Tempfile.new(['invalid', '.txt'])
|
|
file << "Not really an image"
|
|
file.rewind
|
|
|
|
upload.update(url: Discourse.store.store_upload(file, upload), extension: "txt")
|
|
upload
|
|
}
|
|
let(:invalid_image) {
|
|
upload = Fabricate(:upload)
|
|
|
|
file = Tempfile.new(['invalid', '.png'])
|
|
file << "Not really an image"
|
|
file.rewind
|
|
|
|
upload.update(url: Discourse.store.store_upload(file, upload))
|
|
upload
|
|
}
|
|
|
|
it "correctly identifies and stores an image's dominant color" do
|
|
expect(white_image.dominant_color).to eq(nil)
|
|
expect(white_image.dominant_color(calculate_if_missing: true)).to eq("FFFFFF")
|
|
expect(white_image.dominant_color).to eq("FFFFFF")
|
|
|
|
expect(red_image.dominant_color).to eq(nil)
|
|
expect(red_image.dominant_color(calculate_if_missing: true)).to eq("FF0000")
|
|
expect(red_image.dominant_color).to eq("FF0000")
|
|
end
|
|
|
|
it "can be backfilled" do
|
|
expect(white_image.dominant_color).to eq(nil)
|
|
expect(red_image.dominant_color).to eq(nil)
|
|
|
|
Upload.backfill_dominant_colors!(5)
|
|
|
|
white_image.reload
|
|
red_image.reload
|
|
|
|
expect(white_image.dominant_color).to eq("FFFFFF")
|
|
expect(red_image.dominant_color).to eq("FF0000")
|
|
end
|
|
|
|
it "stores an empty string for non-image uploads" do
|
|
expect(not_an_image.dominant_color).to eq(nil)
|
|
expect(not_an_image.dominant_color(calculate_if_missing: true)).to eq("")
|
|
expect(not_an_image.dominant_color).to eq("")
|
|
end
|
|
|
|
it "correctly handles invalid image files" do
|
|
expect(invalid_image.dominant_color).to eq(nil)
|
|
expect(invalid_image.dominant_color(calculate_if_missing: true)).to eq("")
|
|
expect(invalid_image.dominant_color).to eq("")
|
|
end
|
|
|
|
it "correctly handles unparsable ImageMagick output" do
|
|
Discourse::Utils.stubs(:execute_command).returns('someinvalidoutput')
|
|
|
|
expect(invalid_image.dominant_color).to eq(nil)
|
|
|
|
expect {
|
|
invalid_image.dominant_color(calculate_if_missing: true)
|
|
}.to raise_error(/Calculated dominant color but unable to parse output/)
|
|
|
|
expect(invalid_image.dominant_color).to eq(nil)
|
|
end
|
|
end
|
|
end
|