mirror of
https://gh.wpcy.net/https://github.com/discourse/discourse.git
synced 2026-05-06 16:50:46 +08:00
Previously, when polling an RSS feed from a login-required Discourse site, the `api_key` and `api_username` query parameters were passed through in the URL. While #37840 fixed the HEAD→GET issue, API key authentication via query parameters still fails because `api_parameter_allowed?` rejects the request — the route's `path_parameters` aren't populated when `current_user` is evaluated in this context, causing the RouteMatcher to fail the action check. This extracts API credentials from the feed URL query string and sends them as `Api-Key` / `Api-Username` headers instead. Header-based API authentication doesn't go through `api_parameter_allowed?` and works reliably regardless of route matching state. Also adds warn-level logging throughout the feed fetching pipeline: HTTP failures now log the status code, empty/unparseable responses are surfaced, and RSS parse errors include the exception message. Previously all of these failed silently, making diagnosis require console access.
164 lines
5.4 KiB
Ruby
164 lines
5.4 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
RSpec.describe Jobs::DiscourseRssPolling::PollFeed do
|
|
subject(:job) { described_class.new }
|
|
|
|
let(:feed_url) { "https://blog.discourse.org/feed/" }
|
|
let(:author) { Fabricate(:user, trust_level: 1) }
|
|
let(:raw_feed) { file_from_fixtures("feed.rss", "feed") }
|
|
|
|
before { SiteSetting.rss_polling_enabled = true }
|
|
|
|
describe "#execute" do
|
|
before do
|
|
Discourse.redis.del("rss-polling-feed-polled:#{Digest::SHA1.hexdigest(feed_url)}")
|
|
stub_request(:get, feed_url).to_return(status: 200, body: raw_feed)
|
|
end
|
|
|
|
it "creates a topic with the right title, content and author" do
|
|
expect { job.execute(feed_url: feed_url, author_username: author.username) }.to change {
|
|
author.topics.count
|
|
}
|
|
|
|
topic = author.topics.last
|
|
|
|
expect(topic.title).to eq("Poll Feed Spec Fixture")
|
|
expect(topic.first_post.raw).to include("<p>This is the body & content. </p>")
|
|
expect(topic.topic_embed.embed_url).to eq(
|
|
"https://blog.discourse.org/2017/09/poll-feed-spec-fixture",
|
|
)
|
|
end
|
|
|
|
context "with use_pubdate set to false" do
|
|
before do
|
|
SiteSetting.rss_polling_use_pubdate = false
|
|
job.execute(feed_url: feed_url, author_username: author.username)
|
|
end
|
|
|
|
it "has a publication date of now" do
|
|
topic = author.topics.last
|
|
expect(topic.created_at.utc).to be_within(1.second).of Time.now
|
|
expect(topic.first_post.created_at.utc).to be_within(1.second).of Time.now
|
|
end
|
|
end
|
|
|
|
context "with use_pubdate set to true" do
|
|
before do
|
|
SiteSetting.rss_polling_use_pubdate = true
|
|
job.execute(feed_url: feed_url, author_username: author.username)
|
|
end
|
|
|
|
it "has a publication date of the feed" do
|
|
topic = author.topics.last
|
|
expect(topic.created_at).to eq_time(DateTime.parse("2017-09-14 15:22:33.000000000 +0000"))
|
|
expect(topic.first_post.created_at).to eq_time(
|
|
DateTime.parse("2017-09-14 15:22:33.000000000 +0000"),
|
|
)
|
|
end
|
|
end
|
|
|
|
context "with a previous poll on a topic with tags" do
|
|
let(:tag1) { Fabricate(:tag, name: "test-from-rss") }
|
|
let(:tag2) { Fabricate(:tag, name: "test-update-from-rss") }
|
|
|
|
before do
|
|
SiteSetting.tagging_enabled = true
|
|
job.execute(
|
|
feed_url: feed_url,
|
|
author_username: author.username,
|
|
discourse_tags: [tag1.name],
|
|
)
|
|
Discourse.redis.del("rss-polling-feed-polled:#{Digest::SHA1.hexdigest(feed_url)}")
|
|
end
|
|
|
|
context "with rss polling set to true" do
|
|
before { SiteSetting.rss_polling_update_tags = true }
|
|
it "updates tags by default" do
|
|
topic = author.topics.last
|
|
job.execute(
|
|
feed_url: feed_url,
|
|
author_username: author.username,
|
|
discourse_tags: [tag2.name],
|
|
)
|
|
topic = author.topics.last.reload
|
|
expect(topic.tags).to match_array([tag2])
|
|
end
|
|
end
|
|
|
|
context "with rss polling set to false" do
|
|
before { SiteSetting.rss_polling_update_tags = false }
|
|
|
|
it "does not update tags" do
|
|
job.execute(
|
|
feed_url: feed_url,
|
|
author_username: author.username,
|
|
discourse_tags: [tag2.name],
|
|
)
|
|
topic = author.topics.last
|
|
expect(topic.tags).to match_array([tag1])
|
|
end
|
|
end
|
|
end
|
|
|
|
it "is rate limited by rss_polling_frequency" do
|
|
2.times { job.execute(feed_url: feed_url, author_username: author.username) }
|
|
|
|
expect(WebMock).to have_requested(:get, feed_url).once
|
|
end
|
|
|
|
it "is not raising error if http request failed" do
|
|
stub_request(:get, feed_url).to_return(status: 500)
|
|
job.execute(feed_url: feed_url, author_username: author.username)
|
|
end
|
|
|
|
it "skips the topic if the category doesn't exist on our side" do
|
|
invalid_discourse_category_id = 99
|
|
|
|
expect {
|
|
job.execute(
|
|
feed_url: feed_url,
|
|
author_username: author.username,
|
|
discourse_category_id: invalid_discourse_category_id,
|
|
)
|
|
}.not_to change { author.topics.count }
|
|
|
|
expect(author.topics.last).to be_nil
|
|
end
|
|
|
|
it "does not raise error for valid xml but non-rss content" do
|
|
stub_request(:get, feed_url).to_return(status: 200, body: "<html><body>tesing</body></html>")
|
|
|
|
expect {
|
|
job.execute(feed_url: feed_url, author_username: author.username)
|
|
}.not_to raise_error
|
|
end
|
|
|
|
it "does not raise error for valid xml but non-rss title" do
|
|
stub_request(:get, feed_url).to_return(
|
|
status: 200,
|
|
body: rss_polling_file_fixture("mastodon.rss").read,
|
|
)
|
|
|
|
expect {
|
|
job.execute(feed_url: feed_url, author_username: author.username)
|
|
}.not_to raise_error
|
|
end
|
|
|
|
it "sends API credentials as headers instead of query parameters" do
|
|
authenticated_url = "#{feed_url}?api_key=test123&api_username=testuser"
|
|
|
|
Discourse.redis.del("rss-polling-feed-polled:#{Digest::SHA1.hexdigest(authenticated_url)}")
|
|
|
|
stub_request(:get, feed_url).with(
|
|
headers: {
|
|
"Api-Key" => "test123",
|
|
"Api-Username" => "testuser",
|
|
},
|
|
).to_return(status: 200, body: file_from_fixtures("feed.rss", "feed"))
|
|
|
|
expect {
|
|
job.execute(feed_url: authenticated_url, author_username: author.username)
|
|
}.to change { author.topics.count }.by(1)
|
|
end
|
|
end
|
|
end
|