discourse/plugins/discourse-github/spec/jobs/rebake_github_pr_posts_spec.rb
Régis Hanol 67985bff52
PERF: optimize GitHub PR onebox rebaking (#36739)
Previously, when rebaking posts containing GitHub PR oneboxes, we passed
`invalidate_oneboxes: true` to `rebake!` which would re-fetch the onebox
during cooking. This meant each post independently fetched the same PR
data from GitHub.

Now we:

1. Fetch and cache the onebox once upfront via `Oneboxer.preview`
2. Use the new `skip_publish_rebaked_changes` parameter to prevent
publishing intermediate states, without triggering onebox re-fetches
3. Filter posts by `cooked LIKE '%githubpullrequest%'` in SQL rather
than fetching all linked posts and filtering in Ruby

The new `skip_publish_rebaked_changes` parameter on `Post#rebake!`
allows callers to skip the `:rebaked` publish without triggering onebox
invalidation, which is useful when the caller has already refreshed the
cache separately.
2025-12-17 12:06:16 +01:00

104 lines
3.2 KiB
Ruby

# frozen_string_literal: true
RSpec.describe Jobs::RebakeGithubPrPosts do
fab!(:user)
fab!(:topic)
let(:pr_url) { "https://github.com/discourse/discourse/pull/123" }
let(:domain) { "github.com" }
before { enable_current_plugin }
def create_post_with_link(cooked)
Fabricate(:post, topic:, user:, cooked:).tap do |post|
TopicLink.create!(topic:, post:, user:, url: pr_url, domain:)
end
end
describe "#execute" do
before { allow(Oneboxer).to receive(:preview) }
it "does nothing with blank or missing pr_url" do
expect { described_class.new.execute(pr_url: nil) }.not_to raise_error
expect { described_class.new.execute(pr_url: "") }.not_to raise_error
end
it "invalidates the onebox cache for the PR URL" do
described_class.new.execute(pr_url:)
expect(Oneboxer).to have_received(:preview).with(pr_url, invalidate_oneboxes: true)
end
it "rebakes posts with full GitHub PR oneboxes" do
create_post_with_link(<<~HTML)
<aside class="onebox githubpullrequest"><a href="#{pr_url}">PR</a></aside>
HTML
expect_any_instance_of(Post).to receive(:rebake!).with(
priority: :low,
skip_publish_rebaked_changes: true,
)
described_class.new.execute(pr_url:)
end
it "does not rebake posts with plain links or inline oneboxes" do
create_post_with_link(%(<a href="#{pr_url}">plain link</a>))
create_post_with_link(%(<a href="#{pr_url}" class="inline-onebox">inline onebox</a>))
expect_any_instance_of(Post).not_to receive(:rebake!)
described_class.new.execute(pr_url:)
end
it "matches PR URLs with path suffixes like /files or /commits" do
post = create_post_with_link(<<~HTML)
<aside class="onebox githubpullrequest"><a href="#{pr_url}">PR</a></aside>
HTML
TopicLink.create!(topic:, post:, user:, url: "#{pr_url}/files", domain:)
expect_any_instance_of(Post).to receive(:rebake!).with(
priority: :low,
skip_publish_rebaked_changes: true,
)
described_class.new.execute(pr_url:)
end
context "with chat enabled" do
fab!(:chat_channel)
before do
skip("Chat plugin not loaded") unless defined?(::Chat)
SiteSetting.chat_enabled = true
end
def create_chat_message_with_link(cooked)
Fabricate(:chat_message, chat_channel:, user:, cooked:).tap do |message|
Chat::MessageLink.create!(chat_message: message, url: pr_url)
end
end
it "rebakes chat messages with GitHub PR oneboxes and skips notifications" do
create_chat_message_with_link(<<~HTML)
<aside class="onebox githubpullrequest"><a href="#{pr_url}">PR</a></aside>
HTML
expect_any_instance_of(Chat::Message).to receive(:rebake!).with(
priority: :low,
skip_notifications: true,
)
described_class.new.execute(pr_url:)
end
it "does not rebake chat messages without GitHub PR oneboxes" do
create_chat_message_with_link(%(<a href="#{pr_url}">plain link</a>))
expect_any_instance_of(Chat::Message).not_to receive(:rebake!)
described_class.new.execute(pr_url:)
end
end
end
end