# frozen_string_literal: true
RSpec.describe Post do
fab!(:coding_horror) { Fabricate(:coding_horror, refresh_auto_groups: true) }
let(:upload_path) { Discourse.store.upload_path }
before { Oneboxer.stubs :onebox }
it_behaves_like "it has custom fields"
it { is_expected.to have_many(:reviewables).dependent(:destroy) }
describe "#hidden_reasons" do
context "when verifying enum sequence" do
before { @hidden_reasons = Post.hidden_reasons }
it "'flag_threshold_reached' should be at 1st position" do
expect(@hidden_reasons[:flag_threshold_reached]).to eq(1)
end
it "'flagged_by_tl3_user' should be at 4th position" do
expect(@hidden_reasons[:flagged_by_tl3_user]).to eq(4)
end
end
end
describe "#types" do
context "when verifying enum sequence" do
before { @types = Post.types }
it "'regular' should be at 1st position" do
expect(@types[:regular]).to eq(1)
end
it "'whisper' should be at 4th position" do
expect(@types[:whisper]).to eq(4)
end
end
end
describe "#cook_methods" do
context "when verifying enum sequence" do
before { @cook_methods = Post.cook_methods }
it "'regular' should be at 1st position" do
expect(@cook_methods[:regular]).to eq(1)
end
it "'email' should be at 3rd position" do
expect(@cook_methods[:email]).to eq(3)
end
end
end
# Help us build a post with a raw body
def post_with_body(body, user = nil)
args = post_args.merge(raw: body)
args[:user] = user if user.present?
Fabricate.build(:post, args)
end
it { is_expected.to validate_presence_of :raw }
it { is_expected.to validate_length_of(:edit_reason).is_at_most(1000) }
# Min/max body lengths, respecting padding
it { is_expected.not_to allow_value("x").for(:raw) }
it { is_expected.not_to allow_value("x" * (SiteSetting.max_post_length + 1)).for(:raw) }
it { is_expected.not_to allow_value((" " * SiteSetting.min_post_length) + "x").for(:raw) }
it { is_expected.to rate_limit }
fab!(:user) { Fabricate(:user, refresh_auto_groups: true) }
let(:topic) { Fabricate(:topic, user: user) }
let(:post_args) { { user: topic.user, topic: topic } }
describe "scopes" do
describe "#by_newest" do
it "returns posts ordered by created_at desc" do
2.times { |t| Fabricate(:post, created_at: t.seconds.from_now) }
expect(Post.by_newest.first.created_at).to be > Post.by_newest.last.created_at
end
end
describe "#with_user" do
it "gives you a user" do
Fabricate(:post, user: Fabricate.build(:user))
expect(Post.with_user.first.user).to be_a User
end
end
end
describe "revisions and deleting/recovery" do
context "with a post without links" do
let(:post) { Fabricate(:post, post_args) }
before do
post.trash!
post.reload
end
it "doesn't create a new revision when deleted" do
expect(post.revisions.count).to eq(0)
end
describe "recovery" do
before do
post.recover!
post.reload
end
it "doesn't create a new revision when recovered" do
expect(post.revisions.count).to eq(0)
end
end
end
context "with a post with links" do
let(:post) { Fabricate(:post_with_external_links) }
before do
post.trash!
post.reload
end
describe "recovery" do
it "recreates the topic_link records" do
TopicLink.expects(:extract_from).with(post)
post.recover!
end
end
end
end
context "with a post with notices" do
let(:post) do
post = Fabricate(:post, post_args)
post.upsert_custom_fields(
Post::NOTICE => {
type: Post.notices[:returning_user],
last_posted_at: 1.day.ago,
},
)
post
end
it "will have its notice cleared when post is trashed" do
expect { post.trash! }.to change { post.custom_fields }.to({})
end
end
describe "should_secure_uploads?" do
let(:topic) { Fabricate(:topic) }
let!(:post) { Fabricate(:post, topic: topic) }
it "returns false if secure uploads is not enabled" do
expect(post.should_secure_uploads?).to eq(false)
end
context "when secure uploads is enabled" do
before do
setup_s3
SiteSetting.authorized_extensions = "pdf|png|jpg|csv"
SiteSetting.secure_uploads = true
end
context "if login_required" do
before { SiteSetting.login_required = true }
it "returns true" do
expect(post.should_secure_uploads?).to eq(true)
end
context "if secure_uploads_pm_only" do
before { SiteSetting.secure_uploads_pm_only = true }
it "returns false" do
expect(post.should_secure_uploads?).to eq(false)
end
end
end
context "if the topic category is read_restricted" do
let(:category) { Fabricate(:private_category, group: Fabricate(:group)) }
before { topic.change_category_to_id(category.id) }
it "returns true" do
expect(post.should_secure_uploads?).to eq(true)
end
context "when the topic is deleted" do
before do
topic.trash!
post.reload
end
it "returns true" do
expect(post.should_secure_uploads?).to eq(true)
end
end
context "if secure_uploads_pm_only" do
before { SiteSetting.secure_uploads_pm_only = true }
it "returns false" do
expect(post.should_secure_uploads?).to eq(false)
end
end
end
context "if the post is in a PM topic" do
let(:topic) { Fabricate(:private_message_topic) }
it "returns true" do
expect(post.should_secure_uploads?).to eq(true)
end
context "when the topic is deleted" do
before { topic.trash! }
it "returns true" do
expect(post.should_secure_uploads?).to eq(true)
end
end
context "if secure_uploads_pm_only" do
before { SiteSetting.secure_uploads_pm_only = true }
it "returns true" do
expect(post.should_secure_uploads?).to eq(true)
end
end
end
end
end
describe "flagging helpers" do
fab!(:post)
fab!(:user) { coding_horror }
fab!(:admin)
it "is_flagged? is accurate" do
PostActionCreator.off_topic(user, post)
expect(post.reload.is_flagged?).to eq(true)
PostActionDestroyer.destroy(user, post, :off_topic)
expect(post.reload.is_flagged?).to eq(false)
end
it "is_flagged? is true if flag was deferred" do
result = PostActionCreator.off_topic(user, post)
result.reviewable.perform(admin, :ignore_and_do_nothing)
expect(post.reload.is_flagged?).to eq(true)
end
it "is_flagged? is true if flag was cleared" do
result = PostActionCreator.off_topic(user, post)
result.reviewable.perform(admin, :disagree)
expect(post.reload.is_flagged?).to eq(true)
end
it "reviewable_flag is nil when ignored" do
result = PostActionCreator.spam(user, post)
expect(post.reviewable_flag).to eq(result.reviewable)
result.reviewable.perform(admin, :ignore_and_do_nothing)
expect(post.reviewable_flag).to be_nil
end
it "reviewable_flag is nil when disagreed" do
result = PostActionCreator.spam(user, post)
expect(post.reviewable_flag).to eq(result.reviewable)
result.reviewable.perform(admin, :disagree)
expect(post.reload.reviewable_flag).to be_nil
end
end
describe "maximum media embeds" do
fab!(:newuser) { Fabricate(:user, trust_level: TrustLevel[0]) }
let(:post_no_images) { Fabricate.build(:post, post_args.merge(user: newuser)) }
let(:post_one_image) { post_with_body("![sherlock](http://bbc.co.uk/sherlock.jpg)", newuser) }
let(:post_two_images) do
post_with_body(
" ",
newuser,
)
end
let(:post_with_avatars) do
post_with_body(
' ',
newuser,
)
end
let(:post_with_favicon) do
post_with_body('', newuser)
end
let(:post_image_within_quote) do
post_with_body('[quote][/quote]', newuser)
end
let(:post_image_within_code) do
post_with_body('', newuser)
end
let(:post_image_within_pre) { post_with_body('
http://www.google.com
", newuser) }
let(:post_two_links) do
post_with_body(
"discourse twitter",
newuser,
)
end
let(:post_with_mentions) do
post_with_body("hello @#{newuser.username} how are you doing?", newuser)
end
it "returns 0 links for an empty post" do
expect(Fabricate.build(:post).link_count).to eq(0)
end
it "returns 0 links for a post with mentions" do
expect(post_with_mentions.link_count).to eq(0)
end
it "finds links from markdown" do
expect(post_one_link.link_count).to eq(1)
end
it "finds links from HTML" do
expect(post_two_links.link_count).to eq(2)
end
context "with validation" do
before { SiteSetting.newuser_max_links = 1 }
context "with newuser" do
it "returns true when within the amount of links allowed" do
expect(post_one_link).to be_valid
end
it "doesn't allow more links than allowed" do
expect(post_two_links).not_to be_valid
end
end
it "allows multiple links for basic accounts" do
post_two_links.user.trust_level = TrustLevel[1]
expect(post_two_links).to be_valid
end
context "when posting links is limited to certain TL groups" do
it "considers oneboxes links" do
SiteSetting.post_links_allowed_groups = Group::AUTO_GROUPS[:trust_level_3]
post_onebox.user.change_trust_level!(TrustLevel[2])
expect(post_onebox).not_to be_valid
end
it "considers links within code" do
SiteSetting.post_links_allowed_groups = Group::AUTO_GROUPS[:trust_level_3]
post_onebox.user.change_trust_level!(TrustLevel[2])
expect(post_code_link).not_to be_valid
end
it "doesn't allow allow links if user is not in allowed groups" do
SiteSetting.post_links_allowed_groups = Group::AUTO_GROUPS[:trust_level_2]
post_two_links.user.change_trust_level!(TrustLevel[1])
expect(post_one_link).not_to be_valid
end
it "will skip the check for allowlisted domains" do
SiteSetting.allowed_link_domains = "www.bbc.co.uk"
SiteSetting.post_links_allowed_groups = "12"
post_two_links.user.change_trust_level!(TrustLevel[1])
expect(post_one_link).to be_valid
end
end
end
end
describe "@mentions" do
context "with raw_mentions" do
it "returns an empty array with no matches" do
post = Fabricate.build(:post, post_args.merge(raw: "Hello Jake and Finn!"))
expect(post.raw_mentions).to eq([])
end
it "returns lowercase unique versions of the mentions" do
post = Fabricate.build(:post, post_args.merge(raw: "@Jake @Finn @Jake"))
expect(post.raw_mentions).to eq(%w[jake finn])
end
it "ignores pre" do
# we need to force an inline
post = Fabricate.build(:post, post_args.merge(raw: "p @Jake@Finn")) expect(post.raw_mentions).to eq(["finn"]) end it "catches content between pre tags" do # per common mark we need to force an inline post = Fabricate.build(:post, post_args.merge(raw: "a
hello@Finn ")) expect(post.raw_mentions).to eq(["finn"]) end it "ignores code" do post = Fabricate.build(:post, post_args.merge(raw: "@Jake `@Finn`")) expect(post.raw_mentions).to eq(["jake"]) end it "ignores quotes" do post = Fabricate.build( :post, post_args.merge(raw: "[quote=\"Evil Trout\"]\n@Jake\n[/quote]\n@Finn"), ) expect(post.raw_mentions).to eq(["finn"]) end it "handles underscore in username" do post = Fabricate.build(:post, post_args.merge(raw: "@Jake @Finn @Jake_Old")) expect(post.raw_mentions).to eq(%w[jake finn jake_old]) end it "handles hyphen in groupname" do post = Fabricate.build(:post, post_args.merge(raw: "@org-board")) expect(post.raw_mentions).to eq(["org-board"]) end end context "with max mentions" do fab!(:newuser) { Fabricate(:user, trust_level: TrustLevel[0]) } let(:post_with_one_mention) { post_with_body("@Jake is the person I'm mentioning", newuser) } let(:post_with_two_mentions) do post_with_body("@Jake @Finn are the people I'm mentioning", newuser) end context "with new user" do before do SiteSetting.newuser_max_mentions_per_post = 1 SiteSetting.max_mentions_per_post = 5 end it "allows a new user to have newuser_max_mentions_per_post mentions" do expect(post_with_one_mention).to be_valid end it "doesn't allow a new user to have more than newuser_max_mentions_per_post mentions" do expect(post_with_two_mentions).not_to be_valid end end context "when not a new user" do before do SiteSetting.newuser_max_mentions_per_post = 0 SiteSetting.max_mentions_per_post = 1 end it "allows max_mentions_per_post mentions" do post_with_one_mention.user.trust_level = TrustLevel[1] expect(post_with_one_mention).to be_valid end it "doesn't allow to have more than max_mentions_per_post mentions" do post_with_two_mentions.user.trust_level = TrustLevel[1] expect(post_with_two_mentions).not_to be_valid end end end end describe "validation" do it "validates our default post" do expect(Fabricate.build(:post, post_args)).to be_valid end it "create blank posts as invalid" do expect(Fabricate.build(:post, raw: "")).not_to be_valid end end describe "raw_hash" do let(:raw) { "this is our test post body" } let(:post) { post_with_body(raw) } it "returns a value" do expect(post.raw_hash).to be_present end it "returns blank for a nil body" do post.raw = nil expect(post.raw_hash).to be_blank end it "returns the same value for the same raw" do expect(post.raw_hash).to eq(post_with_body(raw).raw_hash) end it "returns a different value for a different raw" do expect(post.raw_hash).not_to eq(post_with_body("something else").raw_hash) end it "returns a different value with different text case" do expect(post.raw_hash).not_to eq(post_with_body("THIS is OUR TEST post BODy").raw_hash) end end describe "revise" do let(:post) { Fabricate(:post, post_args) } let(:first_version_at) { post.last_version_at } it "has no revision" do expect(post.revisions.size).to eq(0) expect(first_version_at).to be_present expect(post.revise(post.user, raw: post.raw)).to eq(false) end context "with the same body" do it "doesn't change version" do expect { post.revise(post.user, raw: post.raw) post.reload }.not_to change(post, :version) end end context "with grace period editing & edit windows" do before { SiteSetting.editing_grace_period = 1.minute.to_i } it "works" do revised_at = post.updated_at + 2.minutes new_revised_at = revised_at + 2.minutes # grace period edit post.revise(post.user, { raw: "updated body" }, revised_at: post.updated_at + 10.seconds) post.reload expect(post.version).to eq(1) expect(post.public_version).to eq(1) expect(post.revisions.size).to eq(0) expect(post.last_version_at.to_i).to eq(first_version_at.to_i) # revision much later post.revise(post.user, { raw: "another updated body" }, revised_at: revised_at) post.reload expect(post.version).to eq(2) expect(post.public_version).to eq(2) expect(post.revisions.size).to eq(1) expect(post.last_version_at.to_i).to eq(revised_at.to_i) # new edit window post.revise( post.user, { raw: "yet another updated body" }, revised_at: revised_at + 10.seconds, ) post.reload expect(post.version).to eq(2) expect(post.public_version).to eq(2) expect(post.revisions.size).to eq(1) expect(post.last_version_at.to_i).to eq(revised_at.to_i) # after second window post.revise( post.user, { raw: "yet another, another updated body" }, revised_at: new_revised_at, ) post.reload expect(post.version).to eq(3) expect(post.public_version).to eq(3) expect(post.revisions.size).to eq(2) expect(post.last_version_at.to_i).to eq(new_revised_at.to_i) end end context "with rate limiter" do let(:changed_by) { coding_horror } it "triggers a rate limiter" do EditRateLimiter.any_instance.expects(:performed!) post.revise(changed_by, raw: "updated body") end end context "with a new body" do let(:changed_by) { coding_horror } let!(:result) { post.revise(changed_by, raw: "updated body") } it "acts correctly" do expect(result).to eq(true) expect(post.raw).to eq("updated body") expect(post.invalidate_oneboxes).to eq(true) expect(post.version).to eq(2) expect(post.public_version).to eq(2) expect(post.revisions.size).to eq(1) expect(post.revisions.first.user).to be_present end context "when second poster posts again quickly" do it "is a grace period edit, because the second poster posted again quickly" do SiteSetting.editing_grace_period = 1.minute.to_i post.revise( changed_by, { raw: "yet another updated body" }, revised_at: post.updated_at + 10.seconds, ) post.reload expect(post.version).to eq(2) expect(post.public_version).to eq(2) expect(post.revisions.size).to eq(1) end end end end describe "before save" do let(:cooked) do "" end let(:post) do Fabricate( :post, raw: "", cooked: cooked, ) end it "should not cook the post if raw has not been changed" do post.save! expect(post.cooked).to eq(cooked) end end describe "after save" do let(:post) { Fabricate(:post, post_args) } it "has correct info set" do expect(post.user_deleted?).to eq(false) expect(post.post_number).to be_present expect(post.excerpt).to be_present expect(post.post_type).to eq(Post.types[:regular]) expect(post.revisions).to be_blank expect(post.cooked).to be_present expect(post.external_id).to be_present expect(post.quote_count).to eq(0) expect(post.replies).to be_blank end context "with extract_quoted_post_numbers" do let!(:post) { Fabricate(:post, post_args) } let(:reply) { Fabricate.build(:post, post_args) } it "finds the quote when in the same topic" do reply.raw = "[quote=\"EvilTrout, post:#{post.post_number}, topic:#{post.topic_id}\"]hello[/quote]" reply.extract_quoted_post_numbers expect(reply.quoted_post_numbers).to eq([post.post_number]) end it "doesn't find the quote in a different topic" do reply.raw = "[quote=\"EvilTrout, post:#{post.post_number}, topic:#{post.topic_id + 1}\"]hello[/quote]" reply.extract_quoted_post_numbers expect(reply.quoted_post_numbers).to be_blank end it "doesn't find the quote in the same post" do reply = Fabricate.build(:post, post_args.merge(post_number: 646)) reply.raw = "[quote=\"EvilTrout, post:#{reply.post_number}, topic:#{post.topic_id}\"]hello[/quote]" reply.extract_quoted_post_numbers expect(reply.quoted_post_numbers).to be_blank end end context "with a new reply" do fab!(:topic) let(:other_user) { coding_horror } let(:reply_text) { "[quote=\"Evil Trout, post:1\"]\nhello\n[/quote]\nHmmm!" } let!(:post) do PostCreator.new(topic.user, raw: Fabricate.build(:post).raw, topic_id: topic.id).create end let!(:reply) do PostCreator.new( other_user, raw: reply_text, topic_id: topic.id, reply_to_post_number: post.post_number, ).create end it "has a quote" do expect(reply.quote_count).to eq(1) end it "has a reply to the user of the original user" do expect(reply.reply_to_user).to eq(post.user) end it "increases the reply count of the parent" do post.reload expect(post.reply_count).to eq(1) end it "increases the reply count of the topic" do topic.reload expect(topic.reply_count).to eq(1) end it "is the child of the parent post" do expect(post.replies).to eq([reply]) end it "doesn't change the post count when you edit the reply" do reply.raw = "updated raw" reply.save post.reload expect(post.reply_count).to eq(1) end context "with a multi-quote reply" do let!(:multi_reply) do raw = "[quote=\"Evil Trout, post:1\"]post1 quote[/quote]\nAha!\n[quote=\"Evil Trout, post:2\"]post2 quote[/quote]\nNeat-o" PostCreator.new( other_user, raw: raw, topic_id: topic.id, reply_to_post_number: post.post_number, ).create end it "has the correct info set" do expect(multi_reply.quote_count).to eq(2) expect(post.replies.include?(multi_reply)).to eq(true) expect(reply.replies.include?(multi_reply)).to eq(true) end end end end describe "summary" do let!(:p1) { Fabricate(:post, post_args.merge(score: 4, percent_rank: 0.33)) } let!(:p2) { Fabricate(:post, post_args.merge(score: 10, percent_rank: 0.66)) } let!(:p3) { Fabricate(:post, post_args.merge(score: 5, percent_rank: 0.99)) } fab!(:p4) { Fabricate(:post, percent_rank: 0.99) } it "returns the OP and posts above the threshold in summary mode" do SiteSetting.summary_percent_filter = 66 expect(Post.summary(topic.id).order(:post_number)).to eq([p1, p2]) expect(Post.summary(p4.topic.id)).to eq([p4]) end end describe "sort_order" do context "with a regular topic" do let!(:p1) { Fabricate(:post, post_args) } let!(:p2) { Fabricate(:post, post_args) } let!(:p3) { Fabricate(:post, post_args) } it "defaults to created order" do expect(Post.regular_order).to eq([p1, p2, p3]) end end end describe "reply_ids" do fab!(:topic) let!(:p1) { Fabricate(:post, topic: topic, post_number: 1) } let!(:p2) { Fabricate(:post, topic: topic, post_number: 2, reply_to_post_number: 1) } let!(:p3) { Fabricate(:post, topic: topic, post_number: 3) } let!(:p4) { Fabricate(:post, topic: topic, post_number: 4, reply_to_post_number: 2) } let!(:p5) { Fabricate(:post, topic: topic, post_number: 5, reply_to_post_number: 4) } let!(:p6) { Fabricate(:post, topic: topic, post_number: 6) } before do PostReply.create!(post: p1, reply: p2) PostReply.create!(post: p2, reply: p4) PostReply.create!(post: p2, reply: p6) # simulates p6 quoting p2 PostReply.create!(post: p3, reply: p5) # simulates p5 quoting p3 PostReply.create!(post: p4, reply: p5) PostReply.create!(post: p6, reply: p6) # https://meta.discourse.org/t/topic-quoting-itself-displays-reply-indicator/76085 end it "returns the reply ids and their level" do expect(p1.reply_ids).to eq( [{ id: p2.id, level: 1 }, { id: p4.id, level: 2 }, { id: p6.id, level: 2 }], ) expect(p2.reply_ids).to eq([{ id: p4.id, level: 1 }, { id: p6.id, level: 1 }]) expect(p3.reply_ids).to be_empty # has no replies expect(p4.reply_ids).to be_empty # p5 replies to 2 posts (p4 and p3) expect(p5.reply_ids).to be_empty # has no replies expect(p6.reply_ids).to be_empty # quotes itself end it "ignores posts moved to other topics" do p2.update_column(:topic_id, Fabricate(:topic).id) expect(p1.reply_ids).to be_blank end it "doesn't include the same reply twice" do PostReply.create!(post: p4, reply: p1) expect(p1.reply_ids.size).to eq(4) end it "does not skip any replies" do expect(p1.reply_ids(only_replies_to_single_post: false)).to eq( [ { id: p2.id, level: 1 }, { id: p4.id, level: 2 }, { id: p5.id, level: 3 }, { id: p6.id, level: 2 }, ], ) expect(p2.reply_ids(only_replies_to_single_post: false)).to eq( [{ id: p4.id, level: 1 }, { id: p5.id, level: 2 }, { id: p6.id, level: 1 }], ) expect(p3.reply_ids(only_replies_to_single_post: false)).to eq([{ id: p5.id, level: 1 }]) expect(p4.reply_ids(only_replies_to_single_post: false)).to eq([{ id: p5.id, level: 1 }]) expect(p5.reply_ids(only_replies_to_single_post: false)).to be_empty # has no replies expect(p6.reply_ids(only_replies_to_single_post: false)).to be_empty # quotes itself end end describe "urls" do it "no-ops for empty list" do expect(Post.urls([])).to eq({}) end # integration test -> should move to centralized integration test it "finds urls for posts presented" do p1 = Fabricate(:post) p2 = Fabricate(:post) expect(Post.urls([p1.id, p2.id])).to eq(p1.id => p1.url, p2.id => p2.url) end end describe "details" do it "adds details" do post = Fabricate.build(:post) post.add_detail("key", "value") expect(post.post_details.size).to eq(1) expect(post.post_details.first.key).to eq("key") expect(post.post_details.first.value).to eq("value") end it "can find a post by a detail" do detail = Fabricate(:post_detail) post = detail.post expect(Post.find_by_detail(detail.key, detail.value).id).to eq(post.id) end end describe "cooking" do let(:post) do Fabricate.build( :post, post_args.merge( raw: "please read my blog http://blog.example.com", user: Fabricate(:user, refresh_auto_groups: true), ), ) end it "should unconditionally follow links for staff" do SiteSetting.tl3_links_no_follow = true post.user.trust_level = 1 post.user.moderator = true post.save expect(post.cooked).not_to match(/nofollow/) end it "should add nofollow to links in the post for trust levels below 3" do post.user.trust_level = 2 post.save expect(post.cooked).to match(/noopener nofollow ugc/) end it "when tl3_links_no_follow is false, should not add nofollow for trust level 3 and higher" do SiteSetting.tl3_links_no_follow = false post.user.trust_level = 3 post.save expect(post.cooked).not_to match(/nofollow/) end it "when tl3_links_no_follow is true, should add nofollow for trust level 3 and higher" do SiteSetting.tl3_links_no_follow = true post.user.trust_level = 3 post.save expect(post.cooked).to match(/noopener nofollow ugc/) end it "passes the last_editor_id as the markdown user_id option and post_id" do post.save post.reload PostAnalyzer .any_instance .expects(:cook) .with( post.raw, { cook_method: Post.cook_methods[:regular], user_id: post.last_editor_id, post_id: post.id, }, ) post.cook(post.raw) user_editor = Fabricate(:user) post.update!(last_editor_id: user_editor.id) PostAnalyzer .any_instance .expects(:cook) .with( post.raw, { cook_method: Post.cook_methods[:regular], user_id: user_editor.id, post_id: post.id }, ) post.cook(post.raw) end describe "mentions" do fab!(:group) do Fabricate( :group, visibility_level: Group.visibility_levels[:members], mentionable_level: Group::ALIAS_LEVELS[:members_mods_and_admins], ) end before { Jobs.run_immediately! } describe "when user can not mention a group" do it "should not create the mention with the notify class" do post = Fabricate(:post, raw: "hello @#{group.name}") post.trigger_post_process post.reload expect(post.cooked).to eq( %Q|
hello @#{group.name}
|, ) end end describe "when user can mention a group" do before { group.add(post.user) } it "should create the mention" do post.update!(raw: "hello @#{group.name}") post.trigger_post_process post.reload expect(post.cooked).to eq( %Q|hello @#{group.name}
|, ) end end describe "when group owner can mention a group" do before do group.update!(mentionable_level: Group::ALIAS_LEVELS[:owners_mods_and_admins]) group.add_owner(post.user) end it "should create the mention" do post.update!(raw: "hello @#{group.name}") post.trigger_post_process post.reload expect(post.cooked).to eq( %Q|hello @#{group.name}
|, ) end end end end describe "has_host_spam" do let(:raw) do "hello from my site http://www.example.net http://#{GlobalSetting.hostname} http://#{RailsMultisite::ConnectionManagement.current_hostname}" end it "correctly detects host spam" do post = Fabricate(:post, raw: raw) expect(post.total_hosts_usage).to eq("www.example.net" => 1) post.acting_user.trust_level = 0 expect(post.has_host_spam?).to eq(false) SiteSetting.newuser_spam_host_threshold = 1 expect(post.has_host_spam?).to eq(true) SiteSetting.allowed_spam_host_domains = "bla.com|boo.com | example.net " expect(post.has_host_spam?).to eq(false) end it "doesn't punish staged users" do SiteSetting.newuser_spam_host_threshold = 1 user = Fabricate(:user, staged: true, trust_level: 0) post = Fabricate(:post, raw: raw, user: user) expect(post.has_host_spam?).to eq(false) end it "punishes previously staged users that were created within 1 day" do SiteSetting.newuser_spam_host_threshold = 1 SiteSetting.newuser_max_links = 3 user = Fabricate(:user, staged: true, trust_level: 0) user.created_at = 1.hour.ago user.unstage! post = Fabricate(:post, raw: raw, user: user) expect(post.has_host_spam?).to eq(true) end it "doesn't punish previously staged users over 1 day old" do SiteSetting.newuser_spam_host_threshold = 1 SiteSetting.newuser_max_links = 3 user = Fabricate(:user, staged: true, trust_level: 0) user.created_at = 2.days.ago user.unstage! post = Fabricate(:post, raw: raw, user: user) expect(post.has_host_spam?).to eq(false) end it "ignores private messages" do SiteSetting.newuser_spam_host_threshold = 1 user = Fabricate(:user, trust_level: 0) post = Fabricate(:post, raw: raw, user: user, topic: Fabricate(:private_message_topic, user: user)) expect(post.has_host_spam?).to eq(false) end end it "has custom fields" do post = Fabricate(:post) expect(post.custom_fields["a"]).to eq(nil) post.custom_fields["Tommy"] = "Hanks" post.custom_fields["Vincent"] = "Vega" post.save post = Post.find(post.id) expect(post.custom_fields).to eq("Tommy" => "Hanks", "Vincent" => "Vega") end describe "#excerpt_for_topic" do it "returns a topic excerpt, defaulting to 220 chars" do expected_excerpt = "This is a sample post with semi-long raw content. The raw content is also more than \ntwo hundred characters to satisfy any test conditions that require content longer \nthan the typical test post raw content. It really is…" post = Fabricate(:post_with_long_raw_content) post.rebake! excerpt = post.excerpt_for_topic expect(excerpt).to eq(expected_excerpt) end it "respects the site setting for topic excerpt" do SiteSetting.topic_excerpt_maxlength = 10 expected_excerpt = "This is a …" post = Fabricate(:post_with_long_raw_content) post.rebake! excerpt = post.excerpt_for_topic expect(excerpt).to eq(expected_excerpt) end end describe "#rebake!" do it "will rebake a post correctly" do post = create_post expect(post.baked_at).not_to eq(nil) first_baked = post.baked_at first_cooked = post.cooked DB.exec("UPDATE posts SET cooked = 'frogs' WHERE id = ?", [post.id]) post.reload post.expects(:publish_change_to_clients!).with(:rebaked) result = post.rebake! expect(post.baked_at).not_to eq_time(first_baked) expect(post.cooked).to eq(first_cooked) expect(result).to eq(true) end it "updates the topic excerpt at the same time if it is the OP" do post = create_post post.topic.update(excerpt: "test") DB.exec("UPDATE posts SET cooked = 'frogs' WHERE id = ?", [post.id]) post.reload result = post.rebake! post.topic.reload expect(post.topic.excerpt).not_to eq("test") end it "does not update the topic excerpt if the post is not the OP" do post = create_post post2 = create_post post.topic.update(excerpt: "test") result = post2.rebake! post.topic.reload expect(post.topic.excerpt).to eq("test") end it "works with posts in deleted topics" do post = create_post post.topic.trash! post.reload post.rebake! end it "uses inline onebox cache by default" do Jobs.run_immediately! stub_request(:get, "http://testonebox.com/vvf").to_return(status: 200, body: <<~HTML)