discourse/spec/components/cooked_post_processor_spec.rb

482 lines
18 KiB
Ruby
Raw Normal View History

2016-04-01 05:33:25 +08:00
require "rails_helper"
2013-11-06 02:04:47 +08:00
require "cooked_post_processor"
2013-02-06 03:16:51 +08:00
describe CookedPostProcessor do
2013-11-06 02:04:47 +08:00
context ".post_process" do
2013-02-06 03:16:51 +08:00
2013-07-08 07:39:08 +08:00
let(:post) { build(:post) }
let(:cpp) { CookedPostProcessor.new(post) }
let(:post_process) { sequence("post_process") }
2013-02-06 03:16:51 +08:00
2013-07-11 04:59:07 +08:00
it "post process in sequence" do
2013-11-06 02:04:47 +08:00
cpp.expects(:keep_reverse_index_up_to_date).in_sequence(post_process)
2013-07-08 07:39:08 +08:00
cpp.expects(:post_process_images).in_sequence(post_process)
cpp.expects(:post_process_oneboxes).in_sequence(post_process)
2013-11-06 02:04:47 +08:00
cpp.expects(:optimize_urls).in_sequence(post_process)
cpp.expects(:pull_hotlinked_images).in_sequence(post_process)
2013-07-08 07:39:08 +08:00
cpp.post_process
2013-02-06 03:16:51 +08:00
end
2013-07-08 07:39:08 +08:00
end
2013-02-06 03:16:51 +08:00
2013-11-06 02:04:47 +08:00
context ".keep_reverse_index_up_to_date" do
2013-10-14 20:27:41 +08:00
2013-11-06 02:04:47 +08:00
let(:post) { build(:post_with_uploads, id: 123) }
2013-10-14 20:27:41 +08:00
let(:cpp) { CookedPostProcessor.new(post) }
2013-11-06 02:04:47 +08:00
it "finds all the uploads in the post" do
Upload.expects(:get_from_url).with("/uploads/default/2/2345678901234567.jpg")
Upload.expects(:get_from_url).with("/uploads/default/1/1234567890123456.jpg")
cpp.keep_reverse_index_up_to_date
2013-10-14 20:27:41 +08:00
end
2013-11-06 02:04:47 +08:00
it "cleans the reverse index up for the current post" do
PostUpload.expects(:delete_all).with(post_id: post.id)
cpp.keep_reverse_index_up_to_date
2013-07-11 04:59:07 +08:00
end
end
2013-11-06 02:04:47 +08:00
context ".post_process_images" do
2013-07-08 07:39:08 +08:00
shared_examples "leave dimensions alone" do
it "doesn't use them" do
# adds the width from the image sizes provided when no dimension is provided
expect(cpp.html).to match(/src="http:\/\/foo.bar\/image.png" width="" height=""/)
# adds the width from the image sizes provided
expect(cpp.html).to match(/src="http:\/\/domain.com\/picture.jpg" width="50" height="42"/)
expect(cpp).to be_dirty
end
end
2013-07-08 07:39:08 +08:00
context "with image_sizes" do
let(:post) { Fabricate(:post_with_image_urls) }
let(:cpp) { CookedPostProcessor.new(post, image_sizes: image_sizes) }
2013-07-08 07:39:08 +08:00
2013-11-26 01:36:13 +08:00
before { cpp.post_process_images }
context "valid" do
let(:image_sizes) { {"http://foo.bar/image.png" => {"width" => 111, "height" => 222}} }
it "use them" do
# adds the width from the image sizes provided when no dimension is provided
expect(cpp.html).to match(/src="http:\/\/foo.bar\/image.png" width="111" height="222"/)
# adds the width from the image sizes provided
expect(cpp.html).to match(/src="http:\/\/domain.com\/picture.jpg" width="50" height="42"/)
expect(cpp).to be_dirty
end
end
context "invalid width" do
let(:image_sizes) { {"http://foo.bar/image.png" => {"width" => 0, "height" => 222}} }
include_examples "leave dimensions alone"
end
context "invalid height" do
let(:image_sizes) { {"http://foo.bar/image.png" => {"width" => 111, "height" => 0}} }
include_examples "leave dimensions alone"
end
context "invalid width & height" do
let(:image_sizes) { {"http://foo.bar/image.png" => {"width" => 0, "height" => 0}} }
include_examples "leave dimensions alone"
2013-02-06 03:16:51 +08:00
end
end
2013-07-08 07:39:08 +08:00
context "with unsized images" do
let(:post) { Fabricate(:post_with_unsized_images) }
2013-07-08 07:39:08 +08:00
let(:cpp) { CookedPostProcessor.new(post) }
2013-06-18 04:46:48 +08:00
2013-07-08 07:39:08 +08:00
it "adds the width and height to images that don't have them" do
FastImage.expects(:size).returns([123, 456])
cpp.post_process_images
2015-01-10 00:34:37 +08:00
expect(cpp.html).to match(/width="123" height="456"/)
expect(cpp).to be_dirty
2013-06-18 04:46:48 +08:00
end
end
2013-07-08 07:39:08 +08:00
context "with large images" do
2013-07-08 07:39:08 +08:00
let(:upload) { Fabricate(:upload) }
let(:post) { Fabricate(:post_with_large_image) }
2013-07-08 07:39:08 +08:00
let(:cpp) { CookedPostProcessor.new(post) }
2013-02-06 03:16:51 +08:00
2013-07-08 07:39:08 +08:00
before do
2014-05-26 19:17:20 +08:00
SiteSetting.max_image_height = 2000
SiteSetting.create_thumbnails = true
2013-07-08 07:39:08 +08:00
Upload.expects(:get_from_url).returns(upload)
FastImage.stubs(:size).returns([1000, 2000])
2014-05-27 11:52:39 +08:00
# hmmm this should be done in a cleaner way
2014-05-27 11:52:39 +08:00
OptimizedImage.expects(:resize).returns(true)
2016-04-01 05:33:25 +08:00
FileStore::BaseStore.any_instance.expects(:get_depth_for).returns(0)
2013-02-06 03:16:51 +08:00
end
2013-07-08 07:39:08 +08:00
it "generates overlay information" do
cpp.post_process_images
2016-04-01 05:33:25 +08:00
expect(cpp.html).to match_html '<p><div class="lightbox-wrapper"><a data-download-href="/uploads/default/e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98" href="/uploads/default/1/1234567890123456.jpg" class="lightbox" title="logo.png"><img src="/uploads/default/optimized/1X/e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98_1_690x1380.png" width="690" height="1380"><div class="meta">
2014-04-15 04:55:57 +08:00
<span class="filename">logo.png</span><span class="informations">1000x2000 1.21 KB</span><span class="expand"></span>
2016-04-01 05:33:25 +08:00
</div></a></div></p>'
2015-01-10 00:34:37 +08:00
expect(cpp).to be_dirty
2013-02-06 03:16:51 +08:00
end
end
context "with title" do
let(:upload) { Fabricate(:upload) }
let(:post) { Fabricate(:post_with_large_image_and_title) }
let(:cpp) { CookedPostProcessor.new(post) }
before do
SiteSetting.max_image_height = 2000
SiteSetting.create_thumbnails = true
Upload.expects(:get_from_url).returns(upload)
FastImage.stubs(:size).returns([1000, 2000])
# hmmm this should be done in a cleaner way
OptimizedImage.expects(:resize).returns(true)
2016-04-01 05:33:25 +08:00
FileStore::BaseStore.any_instance.expects(:get_depth_for).returns(0)
end
it "generates overlay information" do
cpp.post_process_images
2016-04-01 05:33:25 +08:00
expect(cpp.html).to match_html '<p><div class="lightbox-wrapper"><a data-download-href="/uploads/default/e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98" href="/uploads/default/1/1234567890123456.jpg" class="lightbox" title="WAT"><img src="/uploads/default/optimized/1X/e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98_1_690x1380.png" title="WAT" width="690" height="1380"><div class="meta">
<span class="filename">WAT</span><span class="informations">1000x2000 1.21 KB</span><span class="expand"></span>
2016-04-01 05:33:25 +08:00
</div></a></div></p>'
2015-01-10 00:34:37 +08:00
expect(cpp).to be_dirty
end
end
2013-07-08 07:39:08 +08:00
context "topic image" do
2013-07-08 07:39:08 +08:00
let(:topic) { build(:topic, id: 1) }
let(:post) { Fabricate(:post_with_uploaded_image, topic: topic) }
2013-07-08 07:39:08 +08:00
let(:cpp) { CookedPostProcessor.new(post) }
2013-07-08 07:39:08 +08:00
it "adds a topic image if there's one in the post" do
2013-11-06 02:04:47 +08:00
FastImage.stubs(:size)
2015-01-10 00:34:37 +08:00
expect(post.topic.image_url).to eq(nil)
2013-07-08 07:39:08 +08:00
cpp.post_process_images
post.topic.reload
2015-01-10 00:34:37 +08:00
expect(post.topic.image_url).to be_present
end
2013-07-08 07:39:08 +08:00
end
2013-07-08 07:39:08 +08:00
end
2013-06-24 08:10:21 +08:00
2013-11-06 02:04:47 +08:00
context ".extract_images" do
2013-06-24 08:10:21 +08:00
2015-08-05 18:57:31 +08:00
let(:post) { build(:post_with_plenty_of_images) }
2013-11-06 02:04:47 +08:00
let(:cpp) { CookedPostProcessor.new(post) }
2013-07-08 07:39:08 +08:00
2015-08-05 18:57:31 +08:00
it "does not extract emojis or images inside oneboxes or quotes" do
2015-01-10 00:34:37 +08:00
expect(cpp.extract_images.length).to eq(0)
2013-06-24 08:10:21 +08:00
end
2013-11-06 02:04:47 +08:00
end
context ".get_size_from_attributes" do
let(:post) { build(:post) }
let(:cpp) { CookedPostProcessor.new(post) }
it "returns the size when width and height are specified" do
img = { 'src' => 'http://foo.bar/image3.png', 'width' => 50, 'height' => 70}
expect(cpp.get_size_from_attributes(img)).to eq([50, 70])
end
it "returns the size when width and height are floats" do
img = { 'src' => 'http://foo.bar/image3.png', 'width' => 50.2, 'height' => 70.1}
expect(cpp.get_size_from_attributes(img)).to eq([50, 70])
end
it "resizes when only width is specified" do
img = { 'src' => 'http://foo.bar/image3.png', 'width' => 100}
SiteSetting.stubs(:crawl_images?).returns(true)
FastImage.expects(:size).returns([200, 400])
expect(cpp.get_size_from_attributes(img)).to eq([100, 200])
end
it "resizes when only height is specified" do
img = { 'src' => 'http://foo.bar/image3.png', 'height' => 100}
SiteSetting.stubs(:crawl_images?).returns(true)
FastImage.expects(:size).returns([100, 300])
expect(cpp.get_size_from_attributes(img)).to eq([33, 100])
end
end
2013-11-06 02:04:47 +08:00
context ".get_size_from_image_sizes" do
let(:post) { build(:post) }
let(:cpp) { CookedPostProcessor.new(post) }
it "returns the size" do
image_sizes = { "http://my.discourse.org/image.png" => { "width" => 111, "height" => 222 } }
2015-01-10 00:34:37 +08:00
expect(cpp.get_size_from_image_sizes("/image.png", image_sizes)).to eq([111, 222])
2013-07-08 07:39:08 +08:00
end
2013-06-26 08:44:20 +08:00
2013-11-06 02:04:47 +08:00
end
context ".get_size" do
let(:post) { build(:post) }
let(:cpp) { CookedPostProcessor.new(post) }
it "ensures urls are absolute" do
cpp.expects(:is_valid_image_url?).with("http://test.localhost/relative/url/image.png")
cpp.get_size("/relative/url/image.png")
end
it "ensures urls have a default scheme" do
cpp.expects(:is_valid_image_url?).with("http://schemaless.url/image.jpg")
cpp.get_size("//schemaless.url/image.jpg")
end
it "caches the results" do
SiteSetting.stubs(:crawl_images?).returns(true)
FastImage.expects(:size).returns([200, 400])
cpp.get_size("http://foo.bar/image3.png")
2015-01-10 00:34:37 +08:00
expect(cpp.get_size("http://foo.bar/image3.png")).to eq([200, 400])
2013-11-06 02:04:47 +08:00
end
context "when crawl_images is disabled" do
before { SiteSetting.stubs(:crawl_images?).returns(false) }
it "doesn't call FastImage" do
FastImage.expects(:size).never
2015-01-10 00:34:37 +08:00
expect(cpp.get_size("http://foo.bar/image1.png")).to eq(nil)
2013-11-06 02:04:47 +08:00
end
it "is always allowed to crawl our own images" do
store = stub
store.expects(:has_been_uploaded?).returns(true)
Discourse.expects(:store).returns(store)
FastImage.expects(:size).returns([100, 200])
2015-01-10 00:34:37 +08:00
expect(cpp.get_size("http://foo.bar/image2.png")).to eq([100, 200])
2013-11-06 02:04:47 +08:00
end
2016-04-01 05:33:25 +08:00
it "returns nil if FastImage can't get the original size" do
Discourse.store.class.any_instance.expects(:has_been_uploaded?).returns(true)
FastImage.expects(:size).returns(nil)
expect(cpp.get_size("http://foo.bar/image3.png")).to eq(nil)
end
2013-07-08 07:39:08 +08:00
end
2013-06-26 08:44:20 +08:00
2013-07-08 07:39:08 +08:00
end
2013-06-26 08:44:20 +08:00
2013-11-06 02:04:47 +08:00
context ".is_valid_image_url?" do
let(:post) { build(:post) }
let(:cpp) { CookedPostProcessor.new(post) }
it "validates HTTP(s) urls" do
2015-01-10 00:34:37 +08:00
expect(cpp.is_valid_image_url?("http://domain.com")).to eq(true)
expect(cpp.is_valid_image_url?("https://domain.com")).to eq(true)
2013-11-06 02:04:47 +08:00
end
it "doesn't validate other urls" do
2015-01-10 00:34:37 +08:00
expect(cpp.is_valid_image_url?("ftp://domain.com")).to eq(false)
expect(cpp.is_valid_image_url?("ftps://domain.com")).to eq(false)
expect(cpp.is_valid_image_url?("/tmp/image.png")).to eq(false)
expect(cpp.is_valid_image_url?("//domain.com")).to eq(false)
2013-11-06 02:04:47 +08:00
end
it "doesn't throw an exception with a bad URI" do
2015-01-10 00:34:37 +08:00
expect(cpp.is_valid_image_url?("http://do<main.com")).to eq(nil)
2013-11-06 02:04:47 +08:00
end
end
context ".get_filename" do
2013-06-26 08:44:20 +08:00
2013-07-08 07:39:08 +08:00
let(:post) { build(:post) }
let(:cpp) { CookedPostProcessor.new(post) }
it "returns the filename of the src when there is no upload" do
2015-01-10 00:34:37 +08:00
expect(cpp.get_filename(nil, "http://domain.com/image.png")).to eq("image.png")
2013-06-26 08:44:20 +08:00
end
2013-07-08 07:39:08 +08:00
it "returns the original filename of the upload when there is an upload" do
upload = build(:upload, { original_filename: "upload.jpg" })
2015-01-10 00:34:37 +08:00
expect(cpp.get_filename(upload, "http://domain.com/image.png")).to eq("upload.jpg")
2013-07-08 07:39:08 +08:00
end
2013-02-06 03:16:51 +08:00
2013-07-08 07:39:08 +08:00
it "returns a generic name for pasted images" do
upload = build(:upload, { original_filename: "blob.png" })
2015-01-10 00:34:37 +08:00
expect(cpp.get_filename(upload, "http://domain.com/image.png")).to eq(I18n.t('upload.pasted_image_filename'))
2013-02-19 14:57:14 +08:00
end
2013-02-26 00:42:20 +08:00
2013-07-08 07:39:08 +08:00
end
2013-11-06 02:04:47 +08:00
context ".post_process_oneboxes" do
2013-07-08 07:39:08 +08:00
2013-11-06 02:04:47 +08:00
let(:post) { build(:post_with_youtube, id: 123) }
let(:cpp) { CookedPostProcessor.new(post, invalidate_oneboxes: true) }
2013-11-06 02:04:47 +08:00
before do
Oneboxer.expects(:onebox)
.with("http://www.youtube.com/watch?v=9bZkp7q19f0", post_id: 123, invalidate_oneboxes: true)
.returns("<div>GANGNAM STYLE</div>")
cpp.post_process_oneboxes
2013-02-06 03:16:51 +08:00
end
2013-11-06 02:04:47 +08:00
it "is dirty" do
2015-01-10 00:34:37 +08:00
expect(cpp).to be_dirty
2013-11-06 02:04:47 +08:00
end
2013-02-06 03:16:51 +08:00
2013-11-06 02:04:47 +08:00
it "inserts the onebox without wrapping p" do
2015-01-10 00:34:37 +08:00
expect(cpp.html).to match_html "<div>GANGNAM STYLE</div>"
2013-11-06 02:04:47 +08:00
end
2013-11-06 02:04:47 +08:00
end
2013-02-06 03:16:51 +08:00
2013-11-06 02:04:47 +08:00
context ".optimize_urls" do
2013-07-08 07:39:08 +08:00
2013-11-06 02:04:47 +08:00
let(:post) { build(:post_with_uploads_and_links) }
let(:cpp) { CookedPostProcessor.new(post) }
it "uses schemaless url for uploads" do
cpp.optimize_urls
2016-04-01 05:33:25 +08:00
expect(cpp.html).to match_html '<p><a href="//test.localhost/uploads/default/2/2345678901234567.jpg">Link</a><br><img src="//test.localhost/uploads/default/1/1234567890123456.jpg"><br><a href="http://www.google.com" rel="nofollow">Google</a><br><img src="http://foo.bar/image.png"><br><a class="attachment" href="//test.localhost/uploads/default/original/1X/af2c2618032c679333bebf745e75f9088748d737.txt">text.txt</a> (20 Bytes)</p>'
2013-07-08 07:39:08 +08:00
end
2013-11-06 02:04:47 +08:00
context "when CDN is enabled" do
it "does use schemaless CDN url for http uploads" do
2013-11-06 02:04:47 +08:00
Rails.configuration.action_controller.stubs(:asset_host).returns("http://my.cdn.com")
cpp.optimize_urls
2016-04-01 05:33:25 +08:00
expect(cpp.html).to match_html '<p><a href="//my.cdn.com/uploads/default/2/2345678901234567.jpg">Link</a><br><img src="//my.cdn.com/uploads/default/1/1234567890123456.jpg"><br><a href="http://www.google.com" rel="nofollow">Google</a><br><img src="http://foo.bar/image.png"><br><a class="attachment" href="//my.cdn.com/uploads/default/original/1X/af2c2618032c679333bebf745e75f9088748d737.txt">text.txt</a> (20 Bytes)</p>'
2013-11-06 02:04:47 +08:00
end
it "does not use schemaless CDN url for https uploads" do
Rails.configuration.action_controller.stubs(:asset_host).returns("https://my.cdn.com")
cpp.optimize_urls
2016-04-01 05:33:25 +08:00
expect(cpp.html).to match_html '<p><a href="https://my.cdn.com/uploads/default/2/2345678901234567.jpg">Link</a><br><img src="https://my.cdn.com/uploads/default/1/1234567890123456.jpg"><br><a href="http://www.google.com" rel="nofollow">Google</a><br><img src="http://foo.bar/image.png"><br><a class="attachment" href="https://my.cdn.com/uploads/default/original/1X/af2c2618032c679333bebf745e75f9088748d737.txt">text.txt</a> (20 Bytes)</p>'
end
it "does not use CDN when login is required" do
SiteSetting.login_required = true
Rails.configuration.action_controller.stubs(:asset_host).returns("http://my.cdn.com")
cpp.optimize_urls
expect(cpp.html).to match_html '<p><a href="//my.cdn.com/uploads/default/2/2345678901234567.jpg">Link</a><br><img src="//my.cdn.com/uploads/default/1/1234567890123456.jpg"><br><a href="http://www.google.com" rel="nofollow">Google</a><br><img src="http://foo.bar/image.png"><br><a class="attachment" href="//test.localhost/uploads/default/original/1X/af2c2618032c679333bebf745e75f9088748d737.txt">text.txt</a> (20 Bytes)</p>'
end
2013-02-06 03:16:51 +08:00
end
2013-02-06 03:16:51 +08:00
end
2013-11-06 02:04:47 +08:00
context ".pull_hotlinked_images" do
2013-07-08 07:39:08 +08:00
let(:post) { build(:post) }
let(:cpp) { CookedPostProcessor.new(post) }
2013-11-15 23:46:41 +08:00
before { cpp.stubs(:available_disk_space).returns(90) }
it "does not run when download_remote_images_to_local is disabled" do
SiteSetting.stubs(:download_remote_images_to_local).returns(false)
2013-11-06 02:04:47 +08:00
Jobs.expects(:cancel_scheduled_job).never
cpp.pull_hotlinked_images
end
context "when download_remote_images_to_local? is enabled" do
2013-11-06 02:04:47 +08:00
before { SiteSetting.stubs(:download_remote_images_to_local).returns(true) }
2013-11-06 02:04:47 +08:00
it "does not run when there is not enough disk space" do
cpp.expects(:disable_if_low_on_disk_space).returns(true)
2013-11-06 02:04:47 +08:00
Jobs.expects(:cancel_scheduled_job).never
cpp.pull_hotlinked_images
end
context "and there is enough disk space" do
2013-11-15 23:46:41 +08:00
before { cpp.expects(:disable_if_low_on_disk_space).returns(false) }
2013-11-06 02:04:47 +08:00
it "does not run when the system user updated the post" do
post.last_editor_id = Discourse.system_user.id
Jobs.expects(:cancel_scheduled_job).never
cpp.pull_hotlinked_images
end
2013-11-06 02:04:47 +08:00
context "and the post has been updated by an actual user" do
2013-11-06 02:04:47 +08:00
before { post.id = 42 }
2016-04-01 05:33:25 +08:00
it "ensures only one job is scheduled right after the editing_grace_period" do
Jobs.expects(:cancel_scheduled_job).with(:pull_hotlinked_images, post_id: post.id).once
2016-04-01 05:33:25 +08:00
delay = SiteSetting.editing_grace_period + 1
Jobs.expects(:enqueue_in).with(delay.seconds, :pull_hotlinked_images, post_id: post.id, bypass_bump: false).once
cpp.pull_hotlinked_images
end
2013-11-06 02:04:47 +08:00
end
end
2013-06-25 04:56:03 +08:00
end
end
2013-11-15 23:46:41 +08:00
context ".disable_if_low_on_disk_space" do
let(:post) { build(:post) }
let(:cpp) { CookedPostProcessor.new(post) }
before { cpp.expects(:available_disk_space).returns(50) }
it "does nothing when there's enough disk space" do
SiteSetting.expects(:download_remote_images_threshold).returns(20)
SiteSetting.expects(:download_remote_images_to_local).never
2015-01-10 00:34:37 +08:00
expect(cpp.disable_if_low_on_disk_space).to eq(false)
2013-11-15 23:46:41 +08:00
end
context "when there's not enough disk space" do
before { SiteSetting.expects(:download_remote_images_threshold).returns(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
2015-01-10 00:34:37 +08:00
expect(cpp.disable_if_low_on_disk_space).to eq(true)
expect(SiteSetting.download_remote_images_to_local).to eq(false)
end
2013-11-15 23:46:41 +08:00
end
end
context ".is_a_hyperlink?" do
let(:post) { build(:post) }
let(:cpp) { CookedPostProcessor.new(post) }
let(:doc) { Nokogiri::HTML::fragment('<body><div><a><img id="linked_image"></a><p><img id="standard_image"></p></div></body>') }
it "is true when the image is inside a link" do
img = doc.css("img#linked_image").first
2015-01-10 00:34:37 +08:00
expect(cpp.is_a_hyperlink?(img)).to eq(true)
end
it "is false when the image is not inside a link" do
img = doc.css("img#standard_image").first
2015-01-10 00:34:37 +08:00
expect(cpp.is_a_hyperlink?(img)).to eq(false)
end
end
2013-02-06 03:16:51 +08:00
end