mirror of
https://gh.wpcy.net/https://github.com/discourse/discourse.git
synced 2026-05-27 18:59:19 +08:00
When a group or user is renamed, `GroupMentionsUpdater` and `Jobs::UpdateUsername` update the post's raw/cooked content but skip `PostRevisor`, so `categories.description` (a denormalized copy of the first paragraph) is never refreshed. The Edit Category page and category banner keep showing the old name, while the actual topic shows the new one. Same issue with `Post#rebake!` (Rebuild HTML) and `ProcessPost`. Extract `Post#sync_category_description` as the single source of truth for deriving `categories.description` from a post's cooked HTML — both `PostRevisor` and the new `Post#sync_first_post_caches` now delegate to it. `sync_first_post_caches` also updates `topics.excerpt`, centralizing all first-post denormalized field updates. Call `sync_first_post_caches` from every code path that modifies post content outside PostRevisor: `GroupMentionsUpdater`, `UpdateUsername`, `ChangeDisplayName`, `ProcessPost`, and `Post#rebake!`. Skip the update when the description hasn't changed to avoid unnecessary writes. When it has changed, call `publish_category` and `Site.clear_cache` so connected clients see the update immediately (fixes a pre-existing flicker where the old description would briefly appear then get overwritten by stale cached site data). Also add a targeted query in `UpdateUsername` for category description posts authored by the system user, which were missed by the existing `user_actions`-based and self-mention queries. Ref - t/181445
129 lines
4.4 KiB
Ruby
Vendored
129 lines
4.4 KiB
Ruby
Vendored
# frozen_string_literal: true
|
|
|
|
RSpec.describe Jobs::ProcessPost do
|
|
it "returns when the post cannot be found" do
|
|
expect { Jobs::ProcessPost.new.execute(post_id: 1) }.not_to raise_error
|
|
end
|
|
|
|
context "with a post" do
|
|
fab!(:post)
|
|
|
|
it "does not erase posts when CookedPostProcessor malfunctions" do
|
|
# Look kids, an actual reason why you want to use mocks
|
|
CookedPostProcessor.any_instance.expects(:html).returns(" ")
|
|
cooked = post.cooked
|
|
|
|
post.reload
|
|
expect(post.cooked).to eq(cooked)
|
|
|
|
Jobs::ProcessPost.new.execute(post_id: post.id, cook: true)
|
|
end
|
|
|
|
it "recooks if needed" do
|
|
cooked = post.cooked
|
|
|
|
post.update_columns(cooked: "frogs")
|
|
Jobs::ProcessPost.new.execute(post_id: post.id, cook: true)
|
|
|
|
post.reload
|
|
expect(post.cooked).to eq(cooked)
|
|
end
|
|
|
|
it "processes posts" do
|
|
post =
|
|
Fabricate(:post, raw: "<img src='#{Discourse.base_url_no_prefix}/awesome/picture.png'>")
|
|
expect(post.cooked).to match(/http/)
|
|
stub_image_size
|
|
|
|
Jobs::ProcessPost.new.execute(post_id: post.id)
|
|
post.reload
|
|
|
|
# subtle but cooked post processor strip this stuff, this ensures all the code gets a workout
|
|
expect(post.cooked).not_to match(/http/)
|
|
end
|
|
|
|
it "always re-extracts links on post process" do
|
|
post.update_columns(raw: "sam has a blog at https://samsaffron.com")
|
|
expect { Jobs::ProcessPost.new.execute(post_id: post.id) }.to change { TopicLink.count }.by(1)
|
|
end
|
|
|
|
it "extracts links to quoted posts" do
|
|
quoted_post =
|
|
Fabricate(
|
|
:post,
|
|
raw: "This is a post with a link to https://www.discourse.org",
|
|
post_number: 42,
|
|
)
|
|
post.update_columns(
|
|
raw:
|
|
"This quote is the best\n\n[quote=\"#{quoted_post.user.username}, topic:#{quoted_post.topic_id}, post:#{quoted_post.post_number}\"]\n#{quoted_post.excerpt}\n[/quote]",
|
|
)
|
|
stub_image_size
|
|
# when creating a quote, we also create the reflexion link
|
|
expect { Jobs::ProcessPost.new.execute(post_id: post.id) }.to change { TopicLink.count }.by(2)
|
|
end
|
|
|
|
it "extracts links to oneboxed topics" do
|
|
oneboxed_post = Fabricate(:post)
|
|
post.update_columns(raw: "This post is the best\n\n#{oneboxed_post.full_url}")
|
|
stub_image_size
|
|
# when creating a quote, we also create the reflexion link
|
|
expect { Jobs::ProcessPost.new.execute(post_id: post.id) }.to change { TopicLink.count }.by(2)
|
|
end
|
|
|
|
it "works for posts that belong to no existing user" do
|
|
cooked = post.cooked
|
|
|
|
post.update_columns(cooked: "frogs", user_id: nil)
|
|
Jobs::ProcessPost.new.execute(post_id: post.id, cook: true)
|
|
post.reload
|
|
expect(post.cooked).to eq(cooked)
|
|
|
|
post.update_columns(cooked: "frogs", user_id: User.maximum("id") + 1)
|
|
Jobs::ProcessPost.new.execute(post_id: post.id, cook: true)
|
|
post.reload
|
|
expect(post.cooked).to eq(cooked)
|
|
end
|
|
|
|
it "updates first post caches only for the OP" do
|
|
post = Fabricate(:post, raw: "Some OP content", cooked: "")
|
|
post.topic.update_excerpt("Incorrect")
|
|
|
|
Jobs::ProcessPost.new.execute(post_id: post.id)
|
|
expect(post.topic.reload.excerpt).to eq("Some OP content")
|
|
|
|
post2 = Fabricate(:post, raw: "Some reply content", cooked: "", topic: post.topic)
|
|
Jobs::ProcessPost.new.execute(post_id: post2.id)
|
|
expect(post.topic.reload.excerpt).to eq("Some OP content")
|
|
end
|
|
end
|
|
|
|
describe "#enqueue_pull_hotlinked_images" do
|
|
subject(:job) { described_class.new }
|
|
|
|
fab!(:post) { Fabricate(:post, created_at: 20.days.ago) }
|
|
|
|
it "runs even when download_remote_images_to_local is disabled" do
|
|
# We want to run it to pull hotlinked optimized images
|
|
SiteSetting.download_remote_images_to_local = false
|
|
expect_enqueued_with(job: :pull_hotlinked_images, args: { post_id: post.id }) do
|
|
job.execute({ post_id: post.id })
|
|
end
|
|
end
|
|
|
|
context "when download_remote_images_to_local? is enabled" do
|
|
before { SiteSetting.download_remote_images_to_local = true }
|
|
|
|
it "enqueues" do
|
|
expect_enqueued_with(job: :pull_hotlinked_images, args: { post_id: post.id }) do
|
|
job.execute({ post_id: post.id })
|
|
end
|
|
end
|
|
|
|
it "does not run when requested to skip" do
|
|
job.execute({ post_id: post.id, skip_pull_hotlinked_images: true })
|
|
expect(Jobs::PullHotlinkedImages.jobs.size).to eq(0)
|
|
end
|
|
end
|
|
end
|
|
end
|