mirror of
https://github.com/discourse/discourse.git
synced 2024-12-18 21:05:58 +08:00
8ebd5edd1e
This commit renames all secure_media related settings to secure_uploads_* along with the associated functionality. This is being done because "media" does not really cover it, we aren't just doing this for images and videos etc. but for all uploads in the site. Additionally, in future we want to secure more types of uploads, and enable a kind of "mixed mode" where some uploads are secure and some are not, so keeping media in the name is just confusing. This also keeps compatibility with the `secure-media-uploads` path, and changes new secure URLs to be `secure-uploads`. Deprecated settings: * secure_media -> secure_uploads * secure_media_allow_embed_images_in_emails -> secure_uploads_allow_embed_images_in_emails * secure_media_max_email_embed_image_size_kb -> secure_uploads_max_email_embed_image_size_kb
637 lines
24 KiB
Ruby
637 lines
24 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
RSpec.describe Jobs::PullHotlinkedImages do
|
|
let(:image_url) { "http://wiki.mozilla.org/images/2/2e/Longcat1.png" }
|
|
let(:broken_image_url) { "http://wiki.mozilla.org/images/2/2e/Longcat2.png" }
|
|
let(:large_image_url) { "http://wiki.mozilla.org/images/2/2e/Longcat3.png" }
|
|
let(:encoded_image_url) { "https://example.com/אלחוט-.jpg" }
|
|
let(:png) { Base64.decode64("R0lGODlhAQABALMAAAAAAIAAAACAAICAAAAAgIAAgACAgMDAwICAgP8AAAD/AP//AAAA//8A/wD//wBiZCH5BAEAAA8ALAAAAAABAAEAAAQC8EUAOw==") }
|
|
let(:large_png) { Base64.decode64("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") }
|
|
let(:upload_path) { Discourse.store.upload_path }
|
|
|
|
before do
|
|
Jobs.run_immediately!
|
|
|
|
stub_request(:get, image_url).to_return(body: png, headers: { "Content-Type" => "image/png" })
|
|
stub_request(:get, encoded_image_url).to_return(body: png, headers: { "Content-Type" => "image/png" })
|
|
stub_request(:get, broken_image_url).to_return(status: 404)
|
|
stub_request(:get, large_image_url).to_return(body: large_png, headers: { "Content-Type" => "image/png" })
|
|
|
|
stub_request(
|
|
:get,
|
|
"#{Discourse.base_url}/#{upload_path}/original/1X/f59ea56fe8ebe42048491d43a19d9f34c5d0f8dc.gif"
|
|
)
|
|
|
|
stub_request(
|
|
:get,
|
|
"#{Discourse.base_url}/#{upload_path}/original/1X/c530c06cf89c410c0355d7852644a73fc3ec8c04.png"
|
|
)
|
|
|
|
SiteSetting.download_remote_images_to_local = true
|
|
SiteSetting.max_image_size_kb = 2
|
|
SiteSetting.download_remote_images_threshold = 0
|
|
end
|
|
|
|
describe '#execute' do
|
|
before do
|
|
Jobs.run_immediately!
|
|
end
|
|
|
|
it 'does nothing if topic has been deleted' do
|
|
post = Fabricate(:post, raw: "<img src='#{image_url}'>")
|
|
post.topic.destroy!
|
|
|
|
expect do
|
|
Jobs::PullHotlinkedImages.new.execute(post_id: post.id)
|
|
end.not_to change { Upload.count }
|
|
end
|
|
|
|
it 'does nothing if there are no large images to pull' do
|
|
post = Fabricate(:post, raw: 'bob bob')
|
|
orig = post.updated_at
|
|
|
|
freeze_time 1.week.from_now
|
|
Jobs::PullHotlinkedImages.new.execute(post_id: post.id)
|
|
expect(orig).to eq_time(post.reload.updated_at)
|
|
end
|
|
|
|
it 'replaces images' do
|
|
post = Fabricate(:post, raw: "<img src='#{image_url}'>")
|
|
stub_image_size
|
|
|
|
expect { Jobs::PullHotlinkedImages.new.execute(post_id: post.id) }
|
|
.to change { Upload.count }.by(1)
|
|
.and not_change { UserHistory.count } # Should not add to the staff log
|
|
|
|
expect(post.reload.raw).to eq("<img src=\"#{Upload.last.short_url}\">")
|
|
end
|
|
|
|
it 'enqueues raw replacement job with a delay' do
|
|
Jobs.run_later!
|
|
|
|
post = Fabricate(:post, raw: "<img src='#{image_url}'>")
|
|
stub_image_size
|
|
|
|
freeze_time
|
|
Jobs.expects(:cancel_scheduled_job).with(:update_hotlinked_raw, post_id: post.id).once
|
|
delay = SiteSetting.editing_grace_period + 1
|
|
|
|
expect_enqueued_with(job: :update_hotlinked_raw, args: { post_id: post.id }, at: Time.zone.now + delay.seconds) do
|
|
Jobs::PullHotlinkedImages.new.execute(post_id: post.id)
|
|
end
|
|
end
|
|
|
|
it 'removes downloaded images when they are no longer needed' do
|
|
post = Fabricate(:post, raw: "<img src='#{image_url}'>")
|
|
stub_image_size
|
|
post.rebake!
|
|
post.reload
|
|
expect(post.upload_references.count).to eq(1)
|
|
|
|
post.update(raw: "Post with no images")
|
|
post.rebake!
|
|
post.reload
|
|
expect(post.upload_references.count).to eq(0)
|
|
end
|
|
|
|
it 'replaces images again after edit' do
|
|
post = Fabricate(:post, raw: "<img src='#{image_url}'>")
|
|
stub_image_size
|
|
|
|
expect do
|
|
post.rebake!
|
|
end.to change { Upload.count }.by(1)
|
|
|
|
expect(post.reload.raw).to eq("<img src=\"#{Upload.last.short_url}\">")
|
|
|
|
# Post raw is updated back to the old value (e.g. by wordpress integration)
|
|
post.update(raw: "<img src='#{image_url}'>")
|
|
|
|
expect do
|
|
post.rebake!
|
|
end.not_to change { Upload.count } # We alread have the upload
|
|
|
|
expect(post.reload.raw).to eq("<img src=\"#{Upload.last.short_url}\">")
|
|
end
|
|
|
|
it 'replaces encoded image urls' do
|
|
post = Fabricate(:post, raw: "<img src='#{encoded_image_url}'>")
|
|
stub_image_size
|
|
expect do
|
|
Jobs::PullHotlinkedImages.new.execute(post_id: post.id)
|
|
end.to change { Upload.count }.by(1)
|
|
|
|
expect(post.reload.raw).to eq("<img src=\"#{Upload.last.short_url}\">")
|
|
end
|
|
|
|
it 'replaces images in an anchor tag with weird indentation' do
|
|
# Skipped pending https://meta.discourse.org/t/152801
|
|
# This spec was previously passing, even though the resulting markdown was invalid
|
|
# Now the spec has been improved, and shows the issue
|
|
|
|
stub_request(:get, "http://test.localhost/uploads/short-url/z2QSs1KJWoj51uYhDjb6ifCzxH6.gif")
|
|
.to_return(status: 200, body: "")
|
|
|
|
post = Fabricate(:post, raw: <<~MD)
|
|
<h1></h1>
|
|
<a href="https://somelink.com">
|
|
<img alt="somelink" src="#{image_url}">
|
|
</a>
|
|
MD
|
|
|
|
expect do
|
|
Jobs::PullHotlinkedImages.new.execute(post_id: post.id)
|
|
end.to change { Upload.count }.by(1)
|
|
|
|
upload = post.uploads.last
|
|
|
|
expect(post.reload.raw).to eq(<<~MD.chomp)
|
|
<h1></h1>
|
|
<a href="https://somelink.com">
|
|
<img alt="somelink" src="#{upload.short_url}">
|
|
</a>
|
|
MD
|
|
end
|
|
|
|
it 'replaces correct image URL' do
|
|
url = image_url.sub("/2e/Longcat1.png", '')
|
|
post = Fabricate(:post, raw: "[Images](#{url})\n![](#{image_url})")
|
|
stub_image_size
|
|
|
|
expect do
|
|
Jobs::PullHotlinkedImages.new.execute(post_id: post.id)
|
|
end.to change { Upload.count }.by(1)
|
|
|
|
expect(post.reload.raw).to eq("[Images](#{url})\n![](#{Upload.last.short_url})")
|
|
end
|
|
|
|
it 'replaces images without protocol' do
|
|
url = image_url.sub(/^https?\:/, '')
|
|
post = Fabricate(:post, raw: "<img alt='test' src='#{url}'>")
|
|
stub_image_size
|
|
|
|
expect do
|
|
Jobs::PullHotlinkedImages.new.execute(post_id: post.id)
|
|
end.to change { Upload.count }.by(1)
|
|
|
|
expect(post.reload.raw).to eq("<img alt=\"test\" src=\"#{Upload.last.short_url}\">")
|
|
end
|
|
|
|
it 'replaces images without extension' do
|
|
url = image_url.sub(/\.[a-zA-Z0-9]+$/, '')
|
|
stub_request(:get, url).to_return(body: png, headers: { "Content-Type" => "image/png" })
|
|
post = Fabricate(:post, raw: "<img src='#{url}'>")
|
|
stub_image_size
|
|
|
|
expect do
|
|
Jobs::PullHotlinkedImages.new.execute(post_id: post.id)
|
|
end.to change { Upload.count }.by(1)
|
|
|
|
expect(post.reload.raw).to eq("<img src=\"#{Upload.last.short_url}\">")
|
|
end
|
|
|
|
it 'replaces optimized images' do
|
|
optimized_image = Fabricate(:optimized_image)
|
|
url = "#{Discourse.base_url}#{optimized_image.url}"
|
|
|
|
stub_request(:get, url)
|
|
.to_return(status: 200, body: file_from_fixtures("smallest.png"))
|
|
|
|
post = Fabricate(:post, raw: "<img src='#{url}'>")
|
|
stub_image_size
|
|
|
|
expect { Jobs::PullHotlinkedImages.new.execute(post_id: post.id) }
|
|
.to change { Upload.count }.by(1)
|
|
|
|
upload = Upload.last
|
|
post.reload
|
|
|
|
expect(post.raw).to eq("<img src=\"#{Upload.last.short_url}\">")
|
|
expect(post.uploads).to contain_exactly(upload)
|
|
end
|
|
|
|
it "skips editing raw for raw_html posts" do
|
|
raw = "<img src=\"#{image_url}\">"
|
|
post = Fabricate(:post, raw: raw, cook_method: Post.cook_methods[:raw_html])
|
|
stub_image_size
|
|
expect do
|
|
post.rebake!
|
|
post.reload
|
|
end.to change { Upload.count }.by(1)
|
|
|
|
expect(post.raw).to eq(raw)
|
|
end
|
|
|
|
context "when secure uploads enabled for an upload that has already been downloaded and exists" do
|
|
it "doesnt redownload the secure upload" do
|
|
setup_s3
|
|
SiteSetting.secure_uploads = true
|
|
|
|
upload = Fabricate(:secure_upload_s3, secure: true)
|
|
stub_s3(upload)
|
|
url = Upload.secure_uploads_url_from_upload_url(upload.url)
|
|
url = Discourse.base_url + url
|
|
post = Fabricate(:post, raw: "<img src='#{url}'>")
|
|
upload.update(access_control_post: post)
|
|
expect { Jobs::PullHotlinkedImages.new.execute(post_id: post.id) }
|
|
.not_to change { Upload.count }
|
|
end
|
|
|
|
context "when the upload original_sha1 is missing" do
|
|
it "redownloads the upload" do
|
|
setup_s3
|
|
SiteSetting.secure_uploads = true
|
|
|
|
upload = Fabricate(:upload_s3, secure: true)
|
|
stub_s3(upload)
|
|
Upload.stubs(:signed_url_from_secure_uploads_url).returns(upload.url)
|
|
url = Upload.secure_uploads_url_from_upload_url(upload.url)
|
|
url = Discourse.base_url + url
|
|
post = Fabricate(:post, raw: "<img src='#{url}'>")
|
|
upload.update(access_control_post: post)
|
|
FileStore::S3Store.any_instance.stubs(:store_upload).returns(upload.url)
|
|
|
|
# without this we get an infinite hang...
|
|
Post.any_instance.stubs(:trigger_post_process)
|
|
expect { Jobs::PullHotlinkedImages.new.execute(post_id: post.id) }
|
|
.to change { Upload.count }.by(1)
|
|
end
|
|
end
|
|
|
|
context "when the upload access_control_post is different to the current post" do
|
|
it "redownloads the upload" do
|
|
setup_s3
|
|
SiteSetting.secure_uploads = true
|
|
|
|
upload = Fabricate(:secure_upload_s3, secure: true)
|
|
stub_s3(upload)
|
|
Upload.stubs(:signed_url_from_secure_uploads_url).returns(upload.url)
|
|
url = Upload.secure_uploads_url_from_upload_url(upload.url)
|
|
url = Discourse.base_url + url
|
|
post = Fabricate(:post, raw: "<img src='#{url}'>")
|
|
upload.update(access_control_post: Fabricate(:post))
|
|
FileStore::S3Store.any_instance.stubs(:store_upload).returns(upload.url)
|
|
FastImage.expects(:size).returns([100, 100]).at_least_once
|
|
|
|
expect { Jobs::PullHotlinkedImages.new.execute(post_id: post.id) }
|
|
.to change { Upload.count }.by(1)
|
|
|
|
expect { Jobs::PullHotlinkedImages.new.execute(post_id: post.id) }
|
|
.not_to change { Upload.count }
|
|
end
|
|
end
|
|
end
|
|
|
|
it 'replaces markdown image' do
|
|
post = Fabricate(:post, raw: <<~MD)
|
|
[![some test](#{image_url})](https://somelink.com)
|
|
![some test](#{image_url})
|
|
![](#{image_url})
|
|
![abcde](#{image_url} 'some test')
|
|
![](#{image_url} 'some test')
|
|
MD
|
|
stub_image_size
|
|
|
|
expect { Jobs::PullHotlinkedImages.new.execute(post_id: post.id) }
|
|
.to change { Upload.count }.by(1)
|
|
|
|
post.reload
|
|
|
|
expect(post.raw).to eq(<<~MD.chomp)
|
|
[![some test](#{Upload.last.short_url})](https://somelink.com)
|
|
![some test](#{Upload.last.short_url})
|
|
![](#{Upload.last.short_url})
|
|
![abcde](#{Upload.last.short_url} 'some test')
|
|
![](#{Upload.last.short_url} 'some test')
|
|
MD
|
|
end
|
|
|
|
it 'works when invalid url in post' do
|
|
post = Fabricate(:post, raw: <<~MD)
|
|
![some test](#{image_url})
|
|
![some test 2]("#{image_url})
|
|
MD
|
|
stub_image_size
|
|
|
|
expect { Jobs::PullHotlinkedImages.new.execute(post_id: post.id) }
|
|
.to change { Upload.count }.by(1)
|
|
end
|
|
|
|
it 'replaces bbcode images' do
|
|
post = Fabricate(:post, raw: <<~MD)
|
|
[img]
|
|
#{image_url}
|
|
[/img]
|
|
|
|
[img]
|
|
#{image_url}
|
|
[/img]
|
|
MD
|
|
stub_image_size
|
|
|
|
expect { Jobs::PullHotlinkedImages.new.execute(post_id: post.id) }
|
|
.to change { Upload.count }.by(1)
|
|
|
|
post.reload
|
|
|
|
expect(post.raw).to eq(<<~MD.chomp)
|
|
![](#{Upload.last.short_url})
|
|
|
|
![](#{Upload.last.short_url})
|
|
MD
|
|
end
|
|
|
|
describe 'onebox' do
|
|
let(:media) { "File:Brisbane_May_2013201.jpg" }
|
|
let(:url) { "https://commons.wikimedia.org/wiki/#{media}" }
|
|
let(:api_url) { "https://en.wikipedia.org/w/api.php?action=query&titles=#{media}&prop=imageinfo&iilimit=50&iiprop=timestamp|user|url&iiurlwidth=500&format=json" }
|
|
|
|
before do
|
|
stub_request(:head, url)
|
|
stub_request(:get, url).to_return(body: '')
|
|
stub_request(:head, image_url)
|
|
|
|
stub_request(:get, api_url).to_return(body: "{
|
|
\"query\": {
|
|
\"pages\": {
|
|
\"-1\": {
|
|
\"title\": \"#{media}\",
|
|
\"imageinfo\": [{
|
|
\"thumburl\": \"#{image_url}\",
|
|
\"url\": \"#{image_url}\",
|
|
\"descriptionurl\": \"#{url}\"
|
|
}]
|
|
}
|
|
}
|
|
}
|
|
}")
|
|
end
|
|
|
|
it 'replaces image src' do
|
|
post = Fabricate(:post, raw: "#{url}")
|
|
stub_image_size
|
|
|
|
post.rebake!
|
|
post.reload
|
|
|
|
expect(post.cooked).to match(/<img src=.*\/uploads/)
|
|
expect(post.upload_references.count).to eq(1)
|
|
end
|
|
|
|
it 'associates uploads correctly' do
|
|
post = Fabricate(:post, raw: "#{url}")
|
|
stub_image_size
|
|
post.rebake!
|
|
post.reload
|
|
|
|
expect(post.upload_references.count).to eq(1)
|
|
|
|
post.update(raw: "no onebox")
|
|
post.rebake!
|
|
post.reload
|
|
|
|
expect(post.upload_references.count).to eq(0)
|
|
end
|
|
|
|
it 'all combinations' do
|
|
post = Fabricate(:post, raw: <<~MD)
|
|
<img src='#{image_url}'>
|
|
#{url}
|
|
<img src='#{broken_image_url}'>
|
|
<a href='#{url}'><img src='#{large_image_url}'></a>
|
|
#{image_url}
|
|
MD
|
|
stub_image_size
|
|
|
|
2.times do
|
|
post.rebake!
|
|
end
|
|
|
|
post.reload
|
|
|
|
expect(post.raw).to eq(<<~MD.chomp)
|
|
<img src="upload://z2QSs1KJWoj51uYhDjb6ifCzxH6.gif">
|
|
https://commons.wikimedia.org/wiki/File:Brisbane_May_2013201.jpg
|
|
<img src='#{broken_image_url}'>
|
|
<a href='#{url}'><img src='#{large_image_url}'></a>
|
|
![](upload://z2QSs1KJWoj51uYhDjb6ifCzxH6.gif)
|
|
MD
|
|
|
|
expect(post.cooked).to match(/<p><img src=.*\/uploads/)
|
|
expect(post.cooked).to match(/<img src=.*\/uploads.*\ class="thumbnail"/)
|
|
expect(post.cooked).to match(/<span class="broken-image/)
|
|
expect(post.cooked).to match(/<div class="large-image-placeholder">/)
|
|
end
|
|
|
|
it 'rewrites a lone onebox' do
|
|
post = Fabricate(:post, raw: <<~MD)
|
|
Onebox here:
|
|
#{image_url}
|
|
MD
|
|
stub_image_size
|
|
|
|
post.rebake!
|
|
|
|
post.reload
|
|
|
|
expect(post.raw).to eq(<<~MD.chomp)
|
|
Onebox here:
|
|
![](upload://z2QSs1KJWoj51uYhDjb6ifCzxH6.gif)
|
|
MD
|
|
|
|
expect(post.cooked).to match(/<img src=.*\/uploads/)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#should_download_image?' do
|
|
subject { described_class.new }
|
|
|
|
describe 'when url is invalid' do
|
|
it 'should return false' do
|
|
expect(subject.should_download_image?("null")).to eq(false)
|
|
expect(subject.should_download_image?("meta.discourse.org")).to eq(false)
|
|
end
|
|
end
|
|
|
|
describe 'when url is valid' do
|
|
it 'should return true' do
|
|
expect(subject.should_download_image?("http://meta.discourse.org")).to eq(true)
|
|
expect(subject.should_download_image?("//meta.discourse.org")).to eq(true)
|
|
end
|
|
end
|
|
|
|
describe 'when url is an upload' do
|
|
it 'should return false for original' do
|
|
expect(subject.should_download_image?(Fabricate(:upload).url)).to eq(false)
|
|
end
|
|
|
|
context "when secure uploads enabled" do
|
|
it 'should return false for secure-upload url' do
|
|
setup_s3
|
|
SiteSetting.secure_uploads = true
|
|
|
|
upload = Fabricate(:upload_s3, secure: true)
|
|
stub_s3(upload)
|
|
url = Upload.secure_uploads_url_from_upload_url(upload.url)
|
|
expect(subject.should_download_image?(url)).to eq(false)
|
|
end
|
|
end
|
|
|
|
it 'should return true for optimized' do
|
|
src = Discourse.store.get_path_for_optimized_image(Fabricate(:optimized_image))
|
|
expect(subject.should_download_image?(src)).to eq(true)
|
|
end
|
|
end
|
|
|
|
it "returns false for emoji" do
|
|
src = Emoji.url_for("testemoji.png")
|
|
expect(subject.should_download_image?(src)).to eq(false)
|
|
end
|
|
|
|
it "returns false for emoji when app and S3 CDNs configured" do
|
|
setup_s3
|
|
SiteSetting.s3_cdn_url = "https://s3.cdn.com"
|
|
set_cdn_url "https://mydomain.cdn/test"
|
|
|
|
src = UrlHelper.cook_url(Emoji.url_for("testemoji.png"))
|
|
expect(subject.should_download_image?(src)).to eq(false)
|
|
end
|
|
|
|
it "returns false for emoji when emoji CDN configured" do
|
|
SiteSetting.external_emoji_url = "https://emoji.cdn.com"
|
|
|
|
src = UrlHelper.cook_url(Emoji.url_for("testemoji.png"))
|
|
expect(subject.should_download_image?(src)).to eq(false)
|
|
end
|
|
|
|
it "returns false for emoji when app, S3 *and* emoji CDNs configured" do
|
|
setup_s3
|
|
SiteSetting.s3_cdn_url = "https://s3.cdn.com"
|
|
SiteSetting.external_emoji_url = "https://emoji.cdn.com"
|
|
set_cdn_url "https://mydomain.cdn/test"
|
|
|
|
src = UrlHelper.cook_url(Emoji.url_for("testemoji.png"))
|
|
expect(subject.should_download_image?(src)).to eq(false)
|
|
end
|
|
|
|
it "returns false for plugin assets" do
|
|
src = UrlHelper.cook_url("/plugins/discourse-amazing-plugin/myasset.png")
|
|
expect(subject.should_download_image?(src)).to eq(false)
|
|
end
|
|
|
|
it "returns false for local non-uploaded files" do
|
|
src = UrlHelper.cook_url("/mycustomroute.png")
|
|
expect(subject.should_download_image?(src)).to eq(false)
|
|
end
|
|
|
|
context "when download_remote_images_to_local? is false" do
|
|
before do
|
|
SiteSetting.download_remote_images_to_local = false
|
|
end
|
|
|
|
it "still returns true for optimized" do
|
|
src = Discourse.store.get_path_for_optimized_image(Fabricate(:optimized_image))
|
|
expect(subject.should_download_image?(src)).to eq(true)
|
|
end
|
|
|
|
it 'returns false for valid remote URLs' do
|
|
expect(subject.should_download_image?("http://meta.discourse.org")).to eq(false)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "with a lightboxed image" do
|
|
fab!(:upload) { Fabricate(:upload) }
|
|
fab!(:user) { Fabricate(:user) }
|
|
|
|
before do
|
|
FastImage.expects(:size).returns([1750, 2000]).at_least_once
|
|
OptimizedImage.stubs(:resize).returns(true)
|
|
Jobs.run_immediately!
|
|
end
|
|
|
|
it 'replaces missing local uploads in lightbox link' do
|
|
post = PostCreator.create!(
|
|
user,
|
|
raw: "<img src='#{Discourse.base_url}#{upload.url}'>",
|
|
title: "Some title that is long enough"
|
|
)
|
|
|
|
expect(post.reload.cooked).to have_tag(:a, with: { class: "lightbox" })
|
|
|
|
stub_request(:get, "#{Discourse.base_url}#{upload.url}")
|
|
.to_return(status: 200, body: file_from_fixtures("smallest.png"))
|
|
|
|
upload.delete
|
|
|
|
expect { Jobs::PullHotlinkedImages.new.execute(post_id: post.id) }
|
|
.to change { Upload.count }.by(1)
|
|
|
|
post.reload
|
|
|
|
expect(post.raw).to eq("<img src=\"#{Upload.last.short_url}\">")
|
|
expect(post.uploads.count).to eq(1)
|
|
end
|
|
|
|
it "doesn't remove optimized images from lightboxes" do
|
|
post = PostCreator.create!(
|
|
user,
|
|
raw: "![alt](#{upload.short_url})",
|
|
title: "Some title that is long enough"
|
|
)
|
|
|
|
expect(post.reload.cooked).to have_tag(:a, with: { class: "lightbox" })
|
|
|
|
expect { Jobs::PullHotlinkedImages.new.execute(post_id: post.id) }
|
|
.not_to change { Upload.count }
|
|
|
|
post.reload
|
|
|
|
expect(post.raw).to eq("![alt](#{upload.short_url})")
|
|
end
|
|
end
|
|
|
|
describe "#disable_if_low_on_disk_space" do
|
|
fab!(:post) { Fabricate(:post, created_at: 20.days.ago) }
|
|
let(:job) { Jobs::PullHotlinkedImages.new }
|
|
|
|
before do
|
|
SiteSetting.download_remote_images_to_local = true
|
|
SiteSetting.download_remote_images_threshold = 20
|
|
job.stubs(:available_disk_space).returns(50)
|
|
end
|
|
|
|
it "does nothing when there's enough disk space" do
|
|
SiteSetting.expects(:download_remote_images_to_local=).never
|
|
job.execute({ post_id: post.id })
|
|
end
|
|
|
|
context "when there's not enough disk space" do
|
|
|
|
before { SiteSetting.download_remote_images_threshold = 75 }
|
|
|
|
it "disables download_remote_images_threshold and send a notification to the admin" do
|
|
StaffActionLogger.any_instance.expects(:log_site_setting_change).once
|
|
SystemMessage.expects(:create_from_system_user).with(Discourse.site_contact_user, :download_remote_images_disabled).once
|
|
job.execute({ post_id: post.id })
|
|
|
|
expect(SiteSetting.download_remote_images_to_local).to eq(false)
|
|
end
|
|
|
|
it "doesn't disable download_remote_images_to_local if site uses S3" do
|
|
setup_s3
|
|
job.execute({ post_id: post.id })
|
|
|
|
expect(SiteSetting.download_remote_images_to_local).to eq(true)
|
|
end
|
|
|
|
end
|
|
end
|
|
|
|
def stub_s3(upload)
|
|
stub_upload(upload)
|
|
stub_request(:get, "https:" + upload.url).to_return(status: 200, body: file_from_fixtures("smallest.png"))
|
|
end
|
|
end
|