discourse/spec/jobs/pull_hotlinked_images_spec.rb
David Taylor 48e3d5b409
DEV: Add failing test for pull-hotlinked codeblocks (#23682)
If a codeblock contains **exactly** the same markdown as an image which has been retrieved by the 'pull hotlinked' job, then it will be replaced with the new URL. This commit adds failing (skipped) tests for this issue.
2023-09-29 09:55:51 +01:00

685 lines
25 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) do
Base64.decode64(
"R0lGODlhAQABALMAAAAAAIAAAACAAICAAAAAgIAAgACAgMDAwICAgP8AAAD/AP//AAAA//8A/wD//wBiZCH5BAEAAA8ALAAAAAABAAEAAAQC8EUAOw==",
)
end
let(:large_png) do
Base64.decode64(
"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",
)
end
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 { Jobs.run_immediately! }
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,
) { Jobs::PullHotlinkedImages.new.execute(post_id: post.id) }
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 "does not replace images in code blocks", skip: "Known issue" do
post = Fabricate(:post, raw: <<~RAW)
![realimage](#{image_url})
`![codeblockimage](#{image_url})`
RAW
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(<<~RAW)
![realimage](#{Upload.last.short_url})
`![codeblockimage](#{image_url})`
RAW
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)
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) do
"https://en.wikipedia.org/w/api.php?action=query&titles=#{media}&prop=imageinfo&iilimit=50&iiprop=timestamp|user|url&iiurlwidth=500&format=json"
end
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(%r{<img src=.*/uploads.*\ class="thumbnail})
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 { post.rebake! }
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(%r{<p><img src=.*/uploads})
expect(post.cooked).to match(%r{<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(%r{<img src=.*/uploads})
end
end
end
describe "#should_download_image?" do
subject(:job) { described_class.new }
describe "when url is invalid" do
it "should return false" do
expect(job.should_download_image?("null")).to eq(false)
expect(job.should_download_image?("meta.discourse.org")).to eq(false)
end
end
describe "when url is valid" do
it "should return true" do
expect(job.should_download_image?("http://meta.discourse.org")).to eq(true)
expect(job.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(job.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(job.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(job.should_download_image?(src)).to eq(true)
end
end
it "returns false for emoji" do
src = Emoji.url_for("testemoji.png")
expect(job.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(job.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(job.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(job.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(job.should_download_image?(src)).to eq(false)
end
it "returns false for local non-uploaded files" do
src = UrlHelper.cook_url("/mycustomroute.png")
expect(job.should_download_image?(src)).to eq(false)
end
context "when download_remote_images_to_local? is false" do
before { SiteSetting.download_remote_images_to_local = false }
it "still returns true for optimized" do
src = Discourse.store.get_path_for_optimized_image(Fabricate(:optimized_image))
expect(job.should_download_image?(src)).to eq(true)
end
it "returns false for valid remote URLs" do
expect(job.should_download_image?("http://meta.discourse.org")).to eq(false)
end
end
end
describe "with a lightboxed image" do
fab!(:upload) { Fabricate(:large_image_upload) }
fab!(:user) { Fabricate(:user) }
before { Jobs.run_immediately! }
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