[15 min fix] Fallback to the image URL if Cloudinary/Imgproxy are not configured (#13767)
* Fallback to the image URL if neither Cloudinary nor Imgproxy are configured * Fix specs * Cloudinary api_secret is required * Update spec/models/message_spec.rb Co-authored-by: Michael Kohl <citizen428@dev.to> Co-authored-by: Michael Kohl <citizen428@dev.to>
This commit is contained in:
parent
f39b76b865
commit
a798bd2f7d
16 changed files with 95 additions and 27 deletions
|
|
@ -96,10 +96,10 @@ BUFFER_LISTINGS_PROFILE="Optional"
|
|||
|
||||
# Cloudinary for image resizing and cache??
|
||||
# (https://cloudinary.com/documentation/rails_integration)
|
||||
CLOUDINARY_API_KEY="Optional"
|
||||
CLOUDINARY_API_SECRET="Optional"
|
||||
CLOUDINARY_CLOUD_NAME="Optional"
|
||||
CLOUDINARY_SECURE="Optional"
|
||||
CLOUDINARY_API_KEY=
|
||||
CLOUDINARY_API_SECRET=
|
||||
CLOUDINARY_CLOUD_NAME=
|
||||
CLOUDINARY_SECURE=
|
||||
|
||||
# HTML/CSS to Image for generating social preview images
|
||||
# (https://docs.htmlcsstoimage.com/example-code/ruby)
|
||||
|
|
|
|||
|
|
@ -5,8 +5,10 @@ module Images
|
|||
|
||||
if imgproxy_enabled?
|
||||
imgproxy(img_src, **kwargs)
|
||||
else
|
||||
elsif cloudinary_enabled?
|
||||
cloudinary(img_src, **kwargs)
|
||||
else
|
||||
img_src
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -59,6 +61,12 @@ module Images
|
|||
Imgproxy.config.key.present? && Imgproxy.config.salt.present?
|
||||
end
|
||||
|
||||
def self.cloudinary_enabled?
|
||||
config = Cloudinary.config
|
||||
|
||||
config.cloud_name.present? && config.api_key.present? && config.api_secret.present?
|
||||
end
|
||||
|
||||
def self.get_imgproxy_endpoint
|
||||
if Rails.env.production?
|
||||
# Use /images with the same domain on Production as
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
Cloudinary.config do |config|
|
||||
config.cloud_name = Rails.env.test? ? "TEST-CLOUD" : ApplicationConfig["CLOUDINARY_CLOUD_NAME"]
|
||||
config.cloud_name = ApplicationConfig["CLOUDINARY_CLOUD_NAME"]
|
||||
config.api_key = ApplicationConfig["CLOUDINARY_API_KEY"]
|
||||
config.api_secret = ApplicationConfig["CLOUDINARY_API_SECRET"]
|
||||
config.secure = ApplicationConfig["CLOUDINARY_SECURE"]
|
||||
|
|
|
|||
|
|
@ -218,7 +218,7 @@ RSpec.describe ApplicationHelper, type: :helper do
|
|||
end
|
||||
end
|
||||
|
||||
describe "#cloudinary" do
|
||||
describe "#cloudinary", cloudinary: true do
|
||||
it "returns cloudinary-manipulated link" do
|
||||
image = helper.optimized_image_url(Faker::Placeholdit.image)
|
||||
expect(image).to start_with("https://res.cloudinary.com")
|
||||
|
|
@ -240,7 +240,7 @@ RSpec.describe ApplicationHelper, type: :helper do
|
|||
end
|
||||
|
||||
describe "#optimized_image_tag" do
|
||||
it "works just like cl_image_tag" do
|
||||
it "works just like cl_image_tag", cloudinary: true do
|
||||
image_url = "https://i.imgur.com/fKYKgo4.png"
|
||||
cloudinary_image_tag = cl_image_tag(image_url,
|
||||
type: "fetch", crop: "imagga_scale",
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ describe SocialImageHelper do
|
|||
expect(url).to eq article_social_preview_url(article, format: :png, host: "hello.com")
|
||||
end
|
||||
|
||||
it "returns the main image if set" do
|
||||
it "returns the main image if set", cloudinary: true do
|
||||
article.main_image = Faker::CryptoCoin.url_logo
|
||||
|
||||
url = helper.article_social_image_url(article)
|
||||
|
|
@ -66,7 +66,7 @@ describe SocialImageHelper do
|
|||
expect(url).to eq article_social_preview_url(article, format: :png, host: "hello.com")
|
||||
end
|
||||
|
||||
it "returns correct manipulation of cloudinary links" do
|
||||
it "returns correct manipulation of cloudinary links", cloudinary: true do
|
||||
article.update_column(
|
||||
:main_image,
|
||||
"https://res.cloudinary.com/practicaldev/image/fetch/s--A-gun7rr--/c_imagga_scale,f_auto,fl_progressive,h_420,q_auto,w_1000/https://res.cloudinary.com/practicaldev/image/fetch/s--hcD8ZkbP--/c_imagga_scale%2Cf_auto%2Cfl_progressive%2Ch_420%2Cq_auto%2Cw_1000/https://dev-to-uploads.s3.amazonaws.com/i/th93d625o27nuz63oeen.png", # rubocop:disable Layout/LineLength
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ RSpec.describe HtmlVariant, type: :model do
|
|||
expect(described_class.find_for_test(["hello"]).id).to eq(html_variant.id)
|
||||
end
|
||||
|
||||
it "prefixes an image with cloudinary" do
|
||||
it "prefixes an image with cloudinary", cloudinary: true do
|
||||
html = "<div><img src='https://devimages.com/image.jpg' /></div>"
|
||||
html_variant.update(approved: false, html: html)
|
||||
cloudinary_string = "/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_420/https://devimages.com/image.jpg"
|
||||
|
|
|
|||
|
|
@ -43,6 +43,18 @@ RSpec.describe Message, type: :model do
|
|||
|
||||
describe "#message_html" do
|
||||
it "creates rich link with proper link for article" do
|
||||
link = URL.article(article)
|
||||
message.message_markdown = "hello #{link}"
|
||||
message.validate!
|
||||
|
||||
expect(message.message_html).to include(
|
||||
article.title,
|
||||
"sidecar-article",
|
||||
link,
|
||||
)
|
||||
end
|
||||
|
||||
it "creates rich link with proper link for article when Cloudinary is enabled", cloudinary: true do
|
||||
message.message_markdown = "hello http://#{ApplicationConfig['APP_DOMAIN']}#{article.path}"
|
||||
message.validate!
|
||||
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ RSpec.describe PodcastEpisode, type: :model do
|
|||
end
|
||||
end
|
||||
|
||||
describe "Cloudinary configuration and processing" do
|
||||
describe "Cloudinary configuration and processing", cloudinary: true do
|
||||
it "prefixes an image URL with a path" do
|
||||
image_url = "https://dummyimage.com/10x10"
|
||||
podcast_episode.body = "<img src=\"#{image_url}\">"
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ RSpec.describe Html::Parser, type: :service do
|
|||
end
|
||||
|
||||
context "when an image is used" do
|
||||
it "wraps the image with Cloudinary" do
|
||||
it "wraps the image with Cloudinary", cloudinary: true do
|
||||
html = "<img src='https://image.com/image.jpg'>"
|
||||
parsed_html = described_class.new(html).prefix_all_images.html
|
||||
expect(parsed_html).to include("https://res.cloudinary.com")
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ RSpec.describe Images::GenerateSocialImage, type: :labor do
|
|||
expect(described_class.call(article)).to eq(article.main_image)
|
||||
end
|
||||
|
||||
it "returns article social image" do
|
||||
it "returns article social image", cloudinary: true do
|
||||
article.main_image = nil
|
||||
article.social_image = nil
|
||||
article.cached_tag_list = "discuss, hello, goodbye"
|
||||
|
|
|
|||
|
|
@ -13,29 +13,46 @@ RSpec.describe Images::Optimizer, type: :service do
|
|||
|
||||
it "does nothing when given a relative url" do
|
||||
relative_asset_path = "/assets/something.jpg"
|
||||
expect(described_class.call(relative_asset_path)).to eq relative_asset_path
|
||||
expect(described_class.call(relative_asset_path)).to eq(relative_asset_path)
|
||||
end
|
||||
|
||||
it "does nothing when given nil" do
|
||||
expect(described_class.call(nil)).to eq nil
|
||||
expect(described_class.call(nil)).to be(nil)
|
||||
end
|
||||
|
||||
it "returns the image if neither cloudinary nor imgproxy are enabled", :aggregate_failures do
|
||||
allow(described_class).to receive(:cloudinary_enabled?).and_return(false)
|
||||
allow(described_class).to receive(:imgproxy_enabled?).and_return(false)
|
||||
|
||||
expect(described_class.call(image_url)).to eq(image_url)
|
||||
|
||||
expect(described_class).not_to have_received(:cloudinary)
|
||||
expect(described_class).not_to have_received(:imgproxy)
|
||||
end
|
||||
|
||||
it "calls cloudinary if imgproxy is not enabled" do
|
||||
allow(described_class).to receive(:cloudinary_enabled?).and_return(true)
|
||||
allow(described_class).to receive(:imgproxy_enabled?).and_return(false)
|
||||
|
||||
described_class.call(image_url)
|
||||
|
||||
expect(described_class).to have_received(:cloudinary)
|
||||
expect(described_class).not_to have_received(:imgproxy)
|
||||
end
|
||||
|
||||
it "calls imgproxy if imgproxy is enabled" do
|
||||
allow(described_class).to receive(:cloudinary_enabled?).and_return(true)
|
||||
allow(described_class).to receive(:imgproxy_enabled?).and_return(true)
|
||||
|
||||
described_class.call(image_url)
|
||||
|
||||
expect(described_class).not_to have_received(:cloudinary)
|
||||
expect(described_class).to have_received(:imgproxy)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#cloudinary" do
|
||||
describe "#cloudinary", cloudinary: true do
|
||||
it "performs exactly like cl_image_path" do
|
||||
allow(described_class).to receive(:imgproxy_enabled?).and_return(false)
|
||||
cloudinary_url = cl_image_path(image_url,
|
||||
type: "fetch",
|
||||
width: 50, height: 50,
|
||||
|
|
@ -48,7 +65,6 @@ RSpec.describe Images::Optimizer, type: :service do
|
|||
end
|
||||
|
||||
it "generates correct url by relying on DEFAULT_CL_OPTIONS" do
|
||||
allow(described_class).to receive(:imgproxy_enabled?).and_return(false)
|
||||
cloudinary_url = cl_image_path(image_url,
|
||||
type: "fetch",
|
||||
quality: "auto",
|
||||
|
|
@ -67,10 +83,34 @@ RSpec.describe Images::Optimizer, type: :service do
|
|||
end
|
||||
end
|
||||
|
||||
describe "#cloudinary_enabled?" do
|
||||
it "returns false if cloud_name, api_key or api_secret are missing", :aggregate_failures do
|
||||
allow(Cloudinary.config).to receive(:cloud_name).and_return("")
|
||||
expect(described_class.cloudinary_enabled?).to be(false)
|
||||
|
||||
allow(Cloudinary.config).to receive(:cloud_name).and_return("cloud name")
|
||||
allow(Cloudinary.config).to receive(:api_key).and_return("")
|
||||
expect(described_class.cloudinary_enabled?).to be(false)
|
||||
|
||||
allow(Cloudinary.config).to receive(:cloud_name).and_return("cloud name")
|
||||
allow(Cloudinary.config).to receive(:api_key).and_return("api key")
|
||||
allow(Cloudinary.config).to receive(:api_secret).and_return("")
|
||||
expect(described_class.cloudinary_enabled?).to be(false)
|
||||
end
|
||||
|
||||
it "returns true if cloud_name and api_key and api_secret are provided" do
|
||||
allow(Cloudinary.config).to receive(:cloud_name).and_return("cloud name")
|
||||
allow(Cloudinary.config).to receive(:api_key).and_return("api key")
|
||||
allow(Cloudinary.config).to receive(:api_secret).and_return("api secret")
|
||||
|
||||
expect(described_class.cloudinary_enabled?).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#imgproxy_enabled?" do
|
||||
it "returns false if key and salt are missing" do
|
||||
allow(Imgproxy).to receive(:config).and_return(Imgproxy::Config.new)
|
||||
expect(described_class.imgproxy_enabled?).to eq(false)
|
||||
expect(described_class.imgproxy_enabled?).to be(false)
|
||||
end
|
||||
|
||||
it "returns true if key and salt are provided" do
|
||||
|
|
@ -81,7 +121,7 @@ RSpec.describe Images::Optimizer, type: :service do
|
|||
end
|
||||
allow(Imgproxy).to receive(:config).and_return(imgproxy_config_stub)
|
||||
|
||||
expect(described_class.imgproxy_enabled?).to eq(true)
|
||||
expect(described_class.imgproxy_enabled?).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ RSpec.describe Images::Profile, type: :services do
|
|||
end
|
||||
|
||||
context "when user has no profile_image" do
|
||||
it "returns backup image prefixed with Cloudinary" do
|
||||
it "returns backup image prefixed with Cloudinary", cloudinary: true do
|
||||
user = build_stubbed(:user, profile_image: nil)
|
||||
correct_prefix = "/c_fill,f_auto,fl_progressive,h_120,q_auto,w_120/"
|
||||
expect(described_class.call(user.profile_image_url)).to include(correct_prefix + described_class::BACKUP_LINK)
|
||||
|
|
|
|||
|
|
@ -326,7 +326,7 @@ RSpec.describe MarkdownProcessor::Parser, type: :service do
|
|||
expect(generate_and_parse_markdown(markdown_with_img)).to include("<a")
|
||||
end
|
||||
|
||||
it "wraps the image with Cloudinary" do
|
||||
it "wraps the image with Cloudinary", cloudinary: true do
|
||||
expect(generate_and_parse_markdown(markdown_with_img))
|
||||
.to include("https://res.cloudinary.com")
|
||||
end
|
||||
|
|
|
|||
8
spec/support/initializers/image_processing.rb
Normal file
8
spec/support/initializers/image_processing.rb
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
RSpec.configure do |config|
|
||||
config.before(:each, cloudinary: true) do |_example|
|
||||
allow(Cloudinary.config).to receive(:cloud_name).and_return("CLOUD_NAME")
|
||||
allow(Cloudinary.config).to receive(:api_key).and_return("API_KEY")
|
||||
allow(Cloudinary.config).to receive(:api_secret).and_return("API_SECRET")
|
||||
allow(Cloudinary.config).to receive(:secure).and_return("SECURE")
|
||||
end
|
||||
end
|
||||
|
|
@ -43,7 +43,7 @@ RSpec.describe "Using the editor", type: :system do
|
|||
page.evaluate_script("window.onbeforeunload = function(){}")
|
||||
end
|
||||
|
||||
it "fills out form with rich content and click preview" do
|
||||
it "fills out form with rich content and click preview", cloudinary: true do
|
||||
article_body = find("div.crayons-article__body")["innerHTML"]
|
||||
article_body.gsub!(%r{"https://res\.cloudinary\.com/.{1,}"}, "cloudinary_link")
|
||||
|
||||
|
|
@ -58,7 +58,7 @@ RSpec.describe "Using the editor", type: :system do
|
|||
end
|
||||
|
||||
describe "Submitting an article", js: true do
|
||||
it "fill out form and submit" do
|
||||
it "fill out form and submit", cloudinary: true do
|
||||
fill_markdown_with(read_from_file(raw_text))
|
||||
find("button", text: /\ASave changes\z/).click
|
||||
article_body = find(:xpath, "//div[@id='article-body']")["innerHTML"]
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
require "rails_helper"
|
||||
|
||||
RSpec.describe CloudCoverUrl, type: :view_object do
|
||||
RSpec.describe CloudCoverUrl, type: :view_object, cloudinary: true do
|
||||
let(:article) { create(:article) }
|
||||
let(:cloudinary_prefix) { "https://res.cloudinary.com/TEST-CLOUD/image/fetch/" }
|
||||
let(:cloudinary_prefix) { "https://res.cloudinary.com/#{Cloudinary.config.cloud_name}/image/fetch/" }
|
||||
|
||||
it "returns proper url" do
|
||||
expect(described_class.new(article.main_image).call)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue