# frozen_string_literal: true require "cooked_post_processor" require "file_store/s3_store" RSpec.describe CookedPostProcessor do fab!(:upload) fab!(:large_image_upload) fab!(:user_with_auto_groups) { Fabricate(:user, refresh_auto_groups: true) } let(:upload_path) { Discourse.store.upload_path } describe "#post_process" do fab!(:post) { Fabricate(:post, user: user_with_auto_groups, raw: <<~RAW) } RAW let(:cpp) { CookedPostProcessor.new(post, disable_dominant_color: true) } let(:post_process) { sequence("post_process") } it "post process in sequence" do cpp.expects(:post_process_oneboxes).in_sequence(post_process) cpp.expects(:post_process_images).in_sequence(post_process) cpp.expects(:optimize_urls).in_sequence(post_process) cpp.post_process expect(UploadReference.exists?(target: post, upload: upload)).to eq(true) end describe "when post contains oneboxes and inline oneboxes" do let(:url_hostname) { "meta.discourse.org" } let(:url) { "https://#{url_hostname}/t/mini-inline-onebox-support-rfc/66400" } let(:not_oneboxed_url) { "https://#{url_hostname}/t/random-url" } let(:title) { "some title" } let(:post) { Fabricate(:post, user: user_with_auto_groups, raw: <<~RAW) } #{url} This is a #{url} with path #{not_oneboxed_url} This is a https://#{url_hostname}/t/another-random-url test This is a #{url} with path #{url} RAW before do SiteSetting.enable_inline_onebox_on_all_domains = true Oneboxer.stubs(:cached_onebox).with(url).returns <<~HTML HTML Oneboxer.stubs(:cached_onebox).with(not_oneboxed_url).returns(nil) %i[head get].each do |method| stub_request(method, url).to_return(status: 200, body: <<~RAW) #{title} RAW end end after do InlineOneboxer.invalidate(url) Oneboxer.invalidate(url) end it "should respect SiteSetting.max_oneboxes_per_post" do SiteSetting.max_oneboxes_per_post = 2 SiteSetting.add_rel_nofollow_to_user_content = false cpp.post_process expect(cpp.html).to have_tag( "a", with: { href: url, class: "inline-onebox", }, text: title, count: 2, ) expect(cpp.html).to have_tag("aside.onebox a", text: title, count: 1) expect(cpp.html).to have_tag("aside.onebox a", text: url_hostname, count: 1) expect(cpp.html).to have_tag( "a", without: { class: "inline-onebox-loading", }, text: not_oneboxed_url, count: 1, ) expect(cpp.html).to have_tag( "a", without: { class: "onebox", }, text: not_oneboxed_url, count: 1, ) end end describe "when post contains inline oneboxes" do before { SiteSetting.enable_inline_onebox_on_all_domains = true } describe "internal links" do fab!(:topic) fab!(:post) { Fabricate(:post, user: user_with_auto_groups, raw: "Hello #{topic.url}") } let(:url) { topic.url } it "includes the topic title" do cpp.post_process expect(cpp.html).to have_tag( "a", with: { href: UrlHelper.cook_url(url), }, without: { class: "inline-onebox-loading", }, text: topic.title, count: 1, ) topic.update!(title: "Updated to something else") cpp = CookedPostProcessor.new(post, invalidate_oneboxes: true) cpp.post_process expect(cpp.html).to have_tag( "a", with: { href: UrlHelper.cook_url(url), }, without: { class: "inline-onebox-loading", }, text: topic.title, count: 1, ) end end describe "external links" do let(:url_with_path) { "https://meta.discourse.org/t/mini-inline-onebox-support-rfc/66400" } let(:url_with_query_param) { "https://meta.discourse.org?a" } let(:url_no_path) { "https://meta.discourse.org/" } let(:urls) { [url_with_path, url_with_query_param, url_no_path] } let(:title) { "some title" } let(:escaped_title) { CGI.escapeHTML(title) } let(:post) { Fabricate(:post, user: user_with_auto_groups, raw: <<~RAW) } This is a #{url_with_path} topic This should not be inline #{url_no_path} oneboxed - #{url_with_path} - #{url_with_query_param} RAW let(:staff_post) do Fabricate(:post, user: Fabricate(:admin, refresh_auto_groups: true), raw: <<~RAW) This is a #{url_with_path} topic RAW end before do urls.each do |url| stub_request(:get, url).to_return( status: 200, body: "#{escaped_title}", ) end end after { urls.each { |url| InlineOneboxer.invalidate(url) } } it "should convert the right links to inline oneboxes" do cpp.post_process html = cpp.html expect(html).to_not have_tag( "a", with: { href: url_no_path, }, without: { class: "inline-onebox-loading", }, text: title, ) expect(html).to have_tag( "a", with: { href: url_with_path, }, without: { class: "inline-onebox-loading", }, text: title, count: 2, ) expect(html).to have_tag( "a", with: { href: url_with_query_param, }, without: { class: "inline-onebox-loading", }, text: title, count: 1, ) expect(html).to have_tag("a[rel='noopener nofollow ugc']") end it "removes nofollow if user is staff/tl3" do cpp = CookedPostProcessor.new(staff_post, invalidate_oneboxes: true) cpp.post_process expect(cpp.html).to_not have_tag("a[rel='noopener nofollow ugc']") end end end context "when processing images" do before { SiteSetting.responsive_post_image_sizes = "" } context "with responsive images" do before { SiteSetting.responsive_post_image_sizes = "1|1.5|3" } it "includes responsive images on demand" do upload.update!(width: 2000, height: 1500, filesize: 10_000, dominant_color: "FFFFFF") post = Fabricate(:post, user: user_with_auto_groups, raw: "hello ") # fake some optimized images OptimizedImage.create!( url: "/#{upload_path}/666x500.jpg", width: 666, height: 500, upload_id: upload.id, sha1: SecureRandom.hex, extension: ".jpg", filesize: 500, version: OptimizedImage::VERSION, ) # fake 3x optimized image, we lose 2 pixels here over original due to rounding on downsize OptimizedImage.create!( url: "/#{upload_path}/1998x1500.jpg", width: 1998, height: 1500, upload_id: upload.id, sha1: SecureRandom.hex, extension: ".jpg", filesize: 800, ) cpp = CookedPostProcessor.new(post) cpp.add_to_size_cache(upload.url, 2000, 1500) cpp.post_process html = cpp.html expect(html).to include(%Q|data-dominant-color="FFFFFF"|) # 1.5x is skipped cause we have a missing thumb expect(html).to include( "srcset=\"//test.localhost/#{upload_path}/666x500.jpg, //test.localhost/#{upload_path}/1998x1500.jpg 3x\"", ) expect(html).to include("src=\"//test.localhost/#{upload_path}/666x500.jpg\"") # works with CDN set_cdn_url("http://cdn.localhost") cpp = CookedPostProcessor.new(post) cpp.add_to_size_cache(upload.url, 2000, 1500) cpp.post_process html = cpp.html expect(html).to include(%Q|data-dominant-color="FFFFFF"|) expect(html).to include( "srcset=\"//cdn.localhost/#{upload_path}/666x500.jpg, //cdn.localhost/#{upload_path}/1998x1500.jpg 3x\"", ) expect(html).to include("src=\"//cdn.localhost/#{upload_path}/666x500.jpg\"") end it "doesn't include response images for cropped images" do upload.update!(width: 200, height: 4000, filesize: 12_345) post = Fabricate(:post, user: user_with_auto_groups, raw: "hello ") # fake some optimized images OptimizedImage.create!( url: "http://a.b.c/200x500.jpg", width: 200, height: 500, upload_id: upload.id, sha1: SecureRandom.hex, extension: ".jpg", filesize: 500, ) cpp = CookedPostProcessor.new(post) cpp.add_to_size_cache(upload.url, 200, 4000) cpp.post_process expect(cpp.html).to_not include('srcset="') end end shared_examples "leave dimensions alone" do it "doesn't use them" do expect(cpp.html).to match(%r{src="http://foo.bar/image.png" width="" height=""}) expect(cpp.html).to match(%r{src="http://domain.com/picture.jpg" width="50" height="42"}) expect(cpp).to be_dirty end end context "with image_sizes" do fab!(:post) { Fabricate(:post_with_image_urls, user: user_with_auto_groups) } let(:cpp) { CookedPostProcessor.new(post, image_sizes: image_sizes) } before do stub_image_size cpp.post_process end context "when valid" do let(:image_sizes) do { "http://foo.bar/image.png" => { "width" => 111, "height" => 222 } } end it "uses them" do expect(cpp.html).to match(%r{src="http://foo.bar/image.png" width="111" height="222"}) expect(cpp.html).to match( %r{src="http://domain.com/picture.jpg" width="50" height="42"}, ) expect(cpp).to be_dirty end end context "with invalid width" do let(:image_sizes) { { "http://foo.bar/image.png" => { "width" => 0, "height" => 222 } } } include_examples "leave dimensions alone" end context "with invalid height" do let(:image_sizes) { { "http://foo.bar/image.png" => { "width" => 111, "height" => 0 } } } include_examples "leave dimensions alone" end context "with invalid width & height" do let(:image_sizes) { { "http://foo.bar/image.png" => { "width" => 0, "height" => 0 } } } include_examples "leave dimensions alone" end end context "with unsized images" do fab!(:upload) { Fabricate(:image_upload, width: 123, height: 456) } fab!(:post) { Fabricate(:post, user: user_with_auto_groups, raw: <<~HTML) } HTML let(:cpp) { CookedPostProcessor.new(post) } it "adds the width and height to images that don't have them" do cpp.post_process expect(cpp.html).to match(/width="123" height="456"/) expect(cpp).to be_dirty end end context "with large images" do fab!(:upload) { Fabricate(:image_upload, width: 1750, height: 2000) } fab!(:post) { Fabricate(:post, user: user_with_auto_groups, raw: <<~HTML) } HTML let(:cpp) { CookedPostProcessor.new(post, disable_dominant_color: true) } before do SiteSetting.max_image_height = 2000 SiteSetting.create_thumbnails = true end it "generates overlay information" do cpp.post_process expect(cpp.html).to match_html <<~HTML

HTML expect(cpp).to be_dirty end context "when image is inside onebox" do let(:url) { "https://image.com/my-avatar" } let(:post) { Fabricate(:post, user: user_with_auto_groups, raw: url) } before do Oneboxer .stubs(:onebox) .with(url, anything) .returns( "", ) end it "should not add lightbox" do FastImage.expects(:size).returns([1750, 2000]) cpp.post_process expect(cpp.html).to match_html <<~HTML

HTML end end context "when image is an svg" do fab!(:post) do Fabricate( :post, user: user_with_auto_groups, raw: "", ) end it "should not add lightbox" do FastImage.expects(:size).returns([1750, 2000]) cpp.post_process expect(cpp.html).to match_html <<~HTML

HTML end context "when image src is an URL" do let(:post) do Fabricate( :post, user: user_with_auto_groups, raw: "", ) end it "should not add lightbox" do FastImage.expects(:size).returns([1750, 2000]) cpp.post_process expect(cpp.html).to match_html( "

", ) end end end context "with s3_uploads" do let(:upload) { Fabricate(:secure_upload_s3) } before do setup_s3 SiteSetting.s3_cdn_url = "https://s3.cdn.com" SiteSetting.authorized_extensions = "png|jpg|gif|mov|ogg|" stored_path = Discourse.store.get_path_for_upload(upload) upload.update_column(:url, "#{SiteSetting.Upload.absolute_base_url}/#{stored_path}") stub_upload(upload) SiteSetting.login_required = true SiteSetting.secure_uploads = true end let(:optimized_size) { "600x500" } let(:post) do Fabricate( :post, user: user_with_auto_groups, raw: "![large.png|#{optimized_size}](#{upload.short_url})", ) end let(:cooked_html) { <<~HTML }

HTML context "when the upload is attached to the correct post" do before do Discourse .store .class .any_instance .expects(:has_been_uploaded?) .at_least_once .returns(true) upload.update!(secure: true, access_control_post: post) post.link_post_uploads end # TODO fix this spec, it is sometimes getting CDN links when it runs concurrently xit "handles secure images with the correct lightbox link href" do FastImage.expects(:size).returns([1750, 2000]) OptimizedImage.expects(:resize).returns(true) cpp.post_process expect(cpp.html).to match_html cooked_html end context "when the upload was not secure" do before { upload.update!(secure: false) } it "changes the secure status" do cpp.post_process expect(upload.reload.secure).to eq(true) end end context "when the upload should no longer be considered secure" do before { SiteSetting.login_required = false } it "changes the secure status" do cpp.post_process expect(upload.reload.secure).to eq(false) end it "does not use a secure-uploads URL for the lightbox href" do SiteSetting.create_thumbnails = false SiteSetting.max_image_width = 10 SiteSetting.max_image_height = 10 cpp.post_process expect(cpp.html).not_to have_tag( "a", with: { class: "lightbox", href: "//test.localhost/secure-uploads/original/1X/#{upload.sha1}.png", }, ) end end end context "when the upload is attached to a different post" do before do FastImage.size(upload.url) upload.update( secure: true, access_control_post: Fabricate(:post, user: user_with_auto_groups), ) end it "does not create thumbnails or optimize images" do CookedPostProcessor.any_instance.expects(:optimize_image!).never Upload.any_instance.expects(:create_thumbnail!).never stub_image_size cpp.post_process expect(cpp.html).not_to match_html cooked_html end end end end context "with tall images > default aspect ratio" do fab!(:upload) { Fabricate(:image_upload, width: 500, height: 2200) } fab!(:post) { Fabricate(:post, user: user_with_auto_groups, raw: <<~HTML) } HTML let(:cpp) { CookedPostProcessor.new(post, disable_dominant_color: true) } before { SiteSetting.create_thumbnails = true } it "resizes the image instead of crop" do cpp.post_process expect(cpp.html).to match(/width="113" height="500">/) expect(cpp).to be_dirty end end context "with taller images < default aspect ratio" do fab!(:upload) { Fabricate(:image_upload, width: 500, height: 2300) } fab!(:post) { Fabricate(:post, user: user_with_auto_groups, raw: <<~HTML) } HTML let(:cpp) { CookedPostProcessor.new(post, disable_dominant_color: true) } before { SiteSetting.create_thumbnails = true } it "crops the image" do cpp.post_process expect(cpp.html).to match(/width="500" height="500">/) expect(cpp).to be_dirty end end context "with iPhone X screenshots" do fab!(:upload) { Fabricate(:image_upload, width: 1125, height: 2436) } fab!(:post) { Fabricate(:post, user: user_with_auto_groups, raw: <<~HTML) } HTML let(:cpp) { CookedPostProcessor.new(post, disable_dominant_color: true) } before { SiteSetting.create_thumbnails = true } it "crops the image" do cpp.post_process expect(cpp.html).to match_html <<~HTML

HTML expect(cpp).to be_dirty end end context "with large images when using subfolders" do fab!(:upload) { Fabricate(:image_upload, width: 1750, height: 2000) } fab!(:post) { Fabricate(:post, user: user_with_auto_groups, raw: <<~HTML) } HTML let(:cpp) { CookedPostProcessor.new(post, disable_dominant_color: true) } before do set_subfolder "/subfolder" stub_request( :get, "http://#{Discourse.current_hostname}/subfolder#{upload.url}", ).to_return(status: 200, body: File.new(Discourse.store.path_for(upload))) SiteSetting.max_image_height = 2000 SiteSetting.create_thumbnails = true end it "generates overlay information" do cpp.post_process expect(cpp.html).to match_html <<~HTML

HTML expect(cpp).to be_dirty end it "should escape the filename" do upload.update!(original_filename: ">.png") cpp.post_process expect(cpp.html).to match_html <<~HTML

HTML end end context "with title and alt" do fab!(:upload) { Fabricate(:image_upload, width: 1750, height: 2000) } fab!(:post) { Fabricate(:post, user: user_with_auto_groups, raw: <<~HTML) } RED HTML let(:cpp) { CookedPostProcessor.new(post, disable_dominant_color: true) } before do SiteSetting.max_image_height = 2000 SiteSetting.create_thumbnails = true end it "generates overlay information using image title and ignores alt" do cpp.post_process expect(cpp.html).to match_html <<~HTML

HTML expect(cpp).to be_dirty end end context "with title only" do fab!(:upload) { Fabricate(:image_upload, width: 1750, height: 2000) } fab!(:post) { Fabricate(:post, user: user_with_auto_groups, raw: <<~HTML) } HTML let(:cpp) { CookedPostProcessor.new(post, disable_dominant_color: true) } before do SiteSetting.max_image_height = 2000 SiteSetting.create_thumbnails = true end it "generates overlay information using image title" do cpp.post_process expect(cpp.html).to match_html <<~HTML

HTML expect(cpp).to be_dirty end end context "with alt only" do fab!(:upload) { Fabricate(:image_upload, width: 1750, height: 2000) } fab!(:post) { Fabricate(:post, user: user_with_auto_groups, raw: <<~HTML) } RED HTML let(:cpp) { CookedPostProcessor.new(post, disable_dominant_color: true) } before do SiteSetting.max_image_height = 2000 SiteSetting.create_thumbnails = true end it "generates overlay information using image alt" do cpp.post_process expect(cpp.html).to match_html <<~HTML

HTML expect(cpp).to be_dirty end end context "with topic image" do fab!(:post) { Fabricate(:post_with_uploaded_image, user: user_with_auto_groups) } let(:cpp) { CookedPostProcessor.new(post) } it "adds a topic image if there's one in the first post" do FastImage.stubs(:size) expect(post.topic.image_upload_id).to eq(nil) cpp.post_process post.topic.reload expect(post.topic.image_upload_id).to be_present end it "removes image if post is edited and no longer has an image" do FastImage.stubs(:size) cpp.post_process post.topic.reload expect(post.topic.image_upload_id).to be_present expect(post.image_upload_id).to be_present post.update!(raw: "This post no longer has an image.") CookedPostProcessor.new(post).post_process post.topic.reload expect(post.topic.image_upload_id).not_to be_present expect(post.image_upload_id).not_to be_present end it "generates thumbnails correctly" do # image size in cooked is 1500*2000 topic = post.topic cpp.post_process topic.reload expect(topic.image_upload_id).to be_present expect(post.image_upload_id).to be_present post = Fabricate( :post, user: user_with_auto_groups, topic: topic, raw: "this post doesn't have an image", ) CookedPostProcessor.new(post).post_process topic.reload expect(post.topic.image_upload_id).to be_present expect(post.image_upload_id).to be_blank end end it "prioritizes data-thumbnail images" do upload1 = Fabricate(:image_upload, width: 1750, height: 2000) upload2 = Fabricate(:image_upload, width: 1750, height: 2000) post = Fabricate(:post, user: user_with_auto_groups, raw: <<~MD) ![alttext|1750x2000](#{upload1.url}) ![alttext|1750x2000|thumbnail](#{upload2.url}) MD CookedPostProcessor.new(post, disable_dominant_color: true).post_process expect(post.reload.image_upload_id).to eq(upload2.id) end context "with post image" do let(:reply) do Fabricate(:post_with_uploaded_image, user: user_with_auto_groups, post_number: 2) end let(:cpp) { CookedPostProcessor.new(reply) } it "adds a post image if there's one in the post" do FastImage.stubs(:size) expect(reply.image_upload_id).to eq(nil) cpp.post_process reply.reload expect(reply.image_upload_id).to be_present end end end end describe "#extract_images" do let(:post) { build(:post_with_plenty_of_images) } let(:cpp) { CookedPostProcessor.new(post) } it "does not extract emojis or images inside oneboxes or quotes" do expect(cpp.extract_images.length).to eq(0) end end describe "#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 } 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 } FastImage.expects(:size).returns([100, 300]) expect(cpp.get_size_from_attributes(img)).to eq([33, 100]) end it "doesn't raise an error with a weird url" do img = { "src" => nil, "height" => 100 } expect(cpp.get_size_from_attributes(img)).to be_nil end end describe "#get_size_from_image_sizes" do let(:post) { build(:post) } let(:cpp) { CookedPostProcessor.new(post) } let(:image_sizes) do { "http://my.discourse.org/image.png" => { "width" => 111, "height" => 222 } } end it "returns the size" do expect(cpp.get_size_from_image_sizes("/image.png", image_sizes)).to eq([111, 222]) end it "returns nil whe img node has no src" do expect(cpp.get_size_from_image_sizes(nil, image_sizes)).to eq(nil) end end describe "#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 FastImage.expects(:size).returns([200, 400]) cpp.get_size("http://foo.bar/image3.png") expect(cpp.get_size("http://foo.bar/image3.png")).to eq([200, 400]) end end describe "#is_valid_image_url?" do let(:post) { build(:post) } let(:cpp) { CookedPostProcessor.new(post) } it "validates HTTP(s) urls" do expect(cpp.is_valid_image_url?("http://domain.com")).to eq(true) expect(cpp.is_valid_image_url?("https://domain.com")).to eq(true) end it "doesn't validate other urls" do 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) end it "doesn't throw an exception with a bad URI" do expect(cpp.is_valid_image_url?("http://do", ) post = Fabricate(:post, user: user_with_auto_groups, raw: "https://discourse.org") cpp = CookedPostProcessor.new(post, disable_dominant_color: true) cpp.post_process doc = Nokogiri::HTML5.fragment(cpp.html) expect(doc.css(".lightbox-wrapper").size).to eq(0) expect(doc.css("img").first["srcset"]).to eq(nil) expect(doc.css("img").first["src"]).to include("optimized") expect(doc.css("img").first["src"]).to include("512x384") end end describe "#post_process_oneboxes" do let(:post) { build(:post_with_youtube, id: 123) } let(:cpp) { CookedPostProcessor.new(post, invalidate_oneboxes: true) } before do Oneboxer .expects(:onebox) .with( "http://www.youtube.com/watch?v=9bZkp7q19f0", invalidate_oneboxes: true, user_id: nil, category_id: post.topic.category_id, ) .returns("
GANGNAM STYLE
") cpp.post_process_oneboxes end it "inserts the onebox without wrapping p" do expect(cpp).to be_dirty expect(cpp.html).to match_html "
GANGNAM STYLE
" end describe "replacing downloaded onebox image" do let(:url) { "https://image.com/my-avatar" } let(:image_url) { "https://image.com/avatar.png" } it "successfully replaces the image" do Oneboxer .stubs(:onebox) .with(url, anything) .returns("") post = Fabricate(:post, user: user_with_auto_groups, raw: url) upload.update!(url: "https://test.s3.amazonaws.com/something.png", dominant_color: "00ffff") PostHotlinkedMedia.create!( url: "//image.com/avatar.png", post: post, status: "downloaded", upload: upload, ) cpp = CookedPostProcessor.new(post, invalidate_oneboxes: true) stub_image_size(width: 100, height: 200) cpp.post_process_oneboxes expect(cpp.doc.to_s).to eq( "

", ) upload.destroy! cpp = CookedPostProcessor.new(post, invalidate_oneboxes: true) stub_image_size(width: 100, height: 200) cpp.post_process_oneboxes expect(cpp.doc.to_s).to eq( "

", ) Oneboxer.unstub(:onebox) end context "when the post is with_secure_uploads and the upload is secure and secure uploads is enabled" do before do setup_s3 upload.update(secure: true) SiteSetting.login_required = true SiteSetting.secure_uploads = true end it "does not use the direct URL, uses the cooked URL instead (because of the private ACL preventing w/h fetch)" do Oneboxer .stubs(:onebox) .with(url, anything) .returns("") post = Fabricate(:post, user: user_with_auto_groups, raw: url) upload.update!( url: "https://test.s3.amazonaws.com/something.png", dominant_color: "00ffff", ) PostHotlinkedMedia.create!( url: "//image.com/avatar.png", post: post, status: "downloaded", upload: upload, ) cooked_url = "https://localhost/secure-uploads/test.png" UrlHelper.expects(:cook_url).with(upload.url, secure: true).returns(cooked_url) cpp = CookedPostProcessor.new(post, invalidate_oneboxes: true) stub_image_size(width: 100, height: 200) cpp.post_process_oneboxes expect(cpp.doc.to_s).to eq( "

", ) end end end it "replaces large image placeholder" do SiteSetting.max_image_size_kb = 4096 url = "https://image.com/avatar.png" Oneboxer.stubs(:onebox).with(url, anything).returns <<~HTML HTML post = Fabricate(:post, user: user_with_auto_groups, raw: url) PostHotlinkedMedia.create!(url: "//image.com/avatar.png", post: post, status: "too_large") cpp = CookedPostProcessor.new(post, invalidate_oneboxes: true) cpp.post_process expect(cpp.doc.to_s).to match(/
/) expect(cpp.doc.to_s).to include( I18n.t("upload.placeholders.too_large_humanized", max_size: "4 MB"), ) end it "removes large images from onebox" do url = "https://example.com/article" Oneboxer.stubs(:onebox).with(url, anything).returns <<~HTML HTML post = Fabricate(:post, user: user_with_auto_groups, raw: url) PostHotlinkedMedia.create!(url: "//example.com/favicon.ico", post: post, status: "too_large") PostHotlinkedMedia.create!(url: "//example.com/article.jpeg", post: post, status: "too_large") cpp = CookedPostProcessor.new(post, invalidate_oneboxes: true) cpp.post_process expect(cpp.doc).to match_html <<~HTML HTML end it "replaces broken image placeholder" do url = "https://image.com/my-avatar" image_url = "https://image.com/avatar.png" Oneboxer .stubs(:onebox) .with(url, anything) .returns("") post = Fabricate(:post, user: user_with_auto_groups, raw: url) PostHotlinkedMedia.create!( url: "//image.com/avatar.png", post: post, status: "download_failed", ) cpp = CookedPostProcessor.new(post, invalidate_oneboxes: true) cpp.post_process expect(cpp.doc.to_s).to have_tag("span.broken-image") expect(cpp.doc.to_s).to include(I18n.t("post.image_placeholder.broken")) end it "removes broken images from onebox" do url = "https://example.com/article" Oneboxer.stubs(:onebox).with(url, anything).returns <<~HTML HTML post = Fabricate(:post, user: user_with_auto_groups, raw: url) PostHotlinkedMedia.create!( url: "//example.com/favicon.ico", post: post, status: "download_failed", ) PostHotlinkedMedia.create!( url: "//example.com/article.jpeg", post: post, status: "download_failed", ) cpp = CookedPostProcessor.new(post, invalidate_oneboxes: true) cpp.post_process expect(cpp.doc).to match_html <<~HTML HTML end end describe "#post_process_oneboxes removes nofollow if add_rel_nofollow_to_user_content is disabled" do let(:post) { build(:post_with_youtube, id: 123) } let(:cpp) { CookedPostProcessor.new(post, invalidate_oneboxes: true) } before do SiteSetting.add_rel_nofollow_to_user_content = false Oneboxer .expects(:onebox) .with( "http://www.youtube.com/watch?v=9bZkp7q19f0", invalidate_oneboxes: true, user_id: nil, category_id: post.topic.category_id, ) .returns( '', ) cpp.post_process_oneboxes end it "removes nofollow noopener from links" do expect(cpp).to be_dirty expect( cpp.html, ).to match_html '' end end describe "#post_process_oneboxes removes nofollow if user is tl3" do let(:post) { build(:post_with_youtube, id: 123) } let(:cpp) { CookedPostProcessor.new(post, invalidate_oneboxes: true) } before do post.user.trust_level = TrustLevel[3] post.user.save! SiteSetting.add_rel_nofollow_to_user_content = true SiteSetting.tl3_links_no_follow = false Oneboxer .expects(:onebox) .with( "http://www.youtube.com/watch?v=9bZkp7q19f0", invalidate_oneboxes: true, user_id: nil, category_id: post.topic.category_id, ) .returns( '', ) cpp.post_process_oneboxes end it "removes nofollow ugc from links" do expect(cpp).to be_dirty expect( cpp.html, ).to match_html '' end end describe "#post_process_oneboxes with oneboxed image" do let(:post) { build(:post_with_youtube, id: 123) } let(:cpp) { CookedPostProcessor.new(post, invalidate_oneboxes: true) } it "applies aspect ratio to container" do Oneboxer .expects(:onebox) .with( "http://www.youtube.com/watch?v=9bZkp7q19f0", invalidate_oneboxes: true, user_id: nil, category_id: post.topic.category_id, ) .returns( "
", ) cpp.post_process_oneboxes expect(cpp.html).to match_html( '', ) end it "applies aspect ratio when wrapped in link" do Oneboxer .expects(:onebox) .with( "http://www.youtube.com/watch?v=9bZkp7q19f0", invalidate_oneboxes: true, user_id: nil, category_id: post.topic.category_id, ) .returns( "