mirror of
https://github.com/discourse/discourse.git
synced 2024-11-30 23:48:34 +08:00
70269c7c97
We have the periodical job that regularly will rebake old posts. This is used to trickle in update to cooked markdown. The problem is that each rebake can issue multiple background jobs (post process and pull hotlinked images) Previously we had no per-cluster limit so cluster running 100s of sites could flood the sidekiq queue with rebake related jobs. New system introduces a hard limit of 300 rebakes per 15 minutes across a cluster to ensure the sidekiq job is not dominated by this. We also reduced `rebake_old_posts_count` to 80, which is a safer default.
1298 lines
42 KiB
Ruby
1298 lines
42 KiB
Ruby
require 'rails_helper'
|
|
require_dependency 'post_destroyer'
|
|
|
|
describe Post do
|
|
before { Oneboxer.stubs :onebox }
|
|
|
|
describe '#hidden_reasons' do
|
|
context "verify enum sequence" do
|
|
before do
|
|
@hidden_reasons = Post.hidden_reasons
|
|
end
|
|
|
|
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 "verify enum sequence" do
|
|
before do
|
|
@types = Post.types
|
|
end
|
|
|
|
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 "verify enum sequence" do
|
|
before do
|
|
@cook_methods = Post.cook_methods
|
|
end
|
|
|
|
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 }
|
|
|
|
# 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 }
|
|
|
|
let(:topic) { Fabricate(:topic) }
|
|
let(:post_args) do
|
|
{ user: topic.user, topic: topic }
|
|
end
|
|
|
|
describe 'scopes' do
|
|
|
|
describe '#by_newest' do
|
|
it 'returns posts ordered by created_at desc' do
|
|
2.times do |t|
|
|
Fabricate(:post, created_at: t.seconds.from_now)
|
|
end
|
|
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 '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 '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
|
|
|
|
describe 'flagging helpers' do
|
|
let(:post) { Fabricate(:post) }
|
|
let(:user) { Fabricate(:coding_horror) }
|
|
let(:admin) { Fabricate(:admin) }
|
|
|
|
it 'is_flagged? is accurate' do
|
|
PostAction.act(user, post, PostActionType.types[:off_topic])
|
|
post.reload
|
|
expect(post.is_flagged?).to eq(true)
|
|
|
|
PostAction.remove_act(user, post, PostActionType.types[:off_topic])
|
|
post.reload
|
|
expect(post.is_flagged?).to eq(false)
|
|
end
|
|
|
|
it 'is_flagged? is true if flag was deferred' do
|
|
PostAction.act(user, post, PostActionType.types[:off_topic])
|
|
PostAction.defer_flags!(post.reload, admin)
|
|
post.reload
|
|
expect(post.is_flagged?).to eq(true)
|
|
end
|
|
|
|
it 'is_flagged? is true if flag was cleared' do
|
|
PostAction.act(user, post, PostActionType.types[:off_topic])
|
|
PostAction.clear_flags!(post.reload, admin)
|
|
post.reload
|
|
expect(post.is_flagged?).to eq(true)
|
|
end
|
|
|
|
it 'has_active_flag? is false for deferred flags' do
|
|
PostAction.act(user, post, PostActionType.types[:spam])
|
|
post.reload
|
|
expect(post.has_active_flag?).to eq(true)
|
|
|
|
PostAction.defer_flags!(post, admin)
|
|
post.reload
|
|
expect(post.has_active_flag?).to eq(false)
|
|
end
|
|
|
|
it 'has_active_flag? is false for cleared flags' do
|
|
PostAction.act(user, post, PostActionType.types[:spam])
|
|
PostAction.clear_flags!(post.reload, admin)
|
|
post.reload
|
|
expect(post.has_active_flag?).to eq(false)
|
|
end
|
|
end
|
|
|
|
describe "maximum images" do
|
|
let(: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) { post_with_body("<img src='http://discourse.org/logo.png'> <img src='http://bbc.co.uk/sherlock.jpg'>", newuser) }
|
|
let(:post_with_avatars) { post_with_body('<img alt="smiley" title=":smiley:" src="/assets/emoji/smiley.png" class="avatar"> <img alt="wink" title=":wink:" src="/assets/emoji/wink.png" class="avatar">', newuser) }
|
|
let(:post_with_favicon) { post_with_body('<img src="/assets/favicons/wikipedia.png" class="favicon">', newuser) }
|
|
let(:post_image_within_quote) { post_with_body('[quote]<img src="coolimage.png">[/quote]', newuser) }
|
|
let(:post_image_within_code) { post_with_body('<code><img src="coolimage.png"></code>', newuser) }
|
|
let(:post_image_within_pre) { post_with_body('<pre><img src="coolimage.png"></pre>', newuser) }
|
|
let(:post_with_thumbnail) { post_with_body('<img src="/assets/emoji/smiley.png" class="thumbnail">', newuser) }
|
|
let(:post_with_two_classy_images) { post_with_body("<img src='http://discourse.org/logo.png' class='classy'> <img src='http://bbc.co.uk/sherlock.jpg' class='classy'>", newuser) }
|
|
|
|
it "returns 0 images for an empty post" do
|
|
expect(Fabricate.build(:post).image_count).to eq(0)
|
|
end
|
|
|
|
it "finds images from markdown" do
|
|
expect(post_one_image.image_count).to eq(1)
|
|
end
|
|
|
|
it "finds images from HTML" do
|
|
expect(post_two_images.image_count).to eq(2)
|
|
end
|
|
|
|
it "doesn't count avatars as images" do
|
|
expect(post_with_avatars.image_count).to eq(0)
|
|
end
|
|
|
|
it "allows images by default" do
|
|
expect(post_one_image).to be_valid
|
|
end
|
|
|
|
it "doesn't allow more than `min_trust_to_post_images`" do
|
|
SiteSetting.min_trust_to_post_images = 4
|
|
post_one_image.user.trust_level = 3
|
|
expect(post_one_image).not_to be_valid
|
|
end
|
|
|
|
it "doesn't allow more than `min_trust_to_post_images` in a quote" do
|
|
SiteSetting.min_trust_to_post_images = 4
|
|
post_one_image.user.trust_level = 3
|
|
expect(post_image_within_quote).not_to be_valid
|
|
end
|
|
|
|
it "doesn't allow more than `min_trust_to_post_images` in code" do
|
|
SiteSetting.min_trust_to_post_images = 4
|
|
post_one_image.user.trust_level = 3
|
|
expect(post_image_within_code).not_to be_valid
|
|
end
|
|
|
|
it "doesn't allow more than `min_trust_to_post_images` in pre" do
|
|
SiteSetting.min_trust_to_post_images = 4
|
|
post_one_image.user.trust_level = 3
|
|
expect(post_image_within_pre).not_to be_valid
|
|
end
|
|
|
|
it "doesn't allow more than `min_trust_to_post_images`" do
|
|
SiteSetting.min_trust_to_post_images = 4
|
|
post_one_image.user.trust_level = 4
|
|
expect(post_one_image).to be_valid
|
|
end
|
|
|
|
it "doesn't count favicons as images" do
|
|
PrettyText.stubs(:cook).returns(post_with_favicon.raw)
|
|
expect(post_with_favicon.image_count).to eq(0)
|
|
end
|
|
|
|
it "doesn't count thumbnails as images" do
|
|
PrettyText.stubs(:cook).returns(post_with_thumbnail.raw)
|
|
expect(post_with_thumbnail.image_count).to eq(0)
|
|
end
|
|
|
|
it "doesn't count whitelisted images" do
|
|
Post.stubs(:white_listed_image_classes).returns(["classy"])
|
|
# I dislike this, but passing in a custom whitelist is hard
|
|
PrettyText.stubs(:cook).returns(post_with_two_classy_images.raw)
|
|
expect(post_with_two_classy_images.image_count).to eq(0)
|
|
end
|
|
|
|
context "validation" do
|
|
|
|
before do
|
|
SiteSetting.newuser_max_images = 1
|
|
end
|
|
|
|
context 'newuser' do
|
|
it "allows a new user to post below the limit" do
|
|
expect(post_one_image).to be_valid
|
|
end
|
|
|
|
it "doesn't allow more than the maximum" do
|
|
expect(post_two_images).not_to be_valid
|
|
end
|
|
|
|
it "doesn't allow a new user to edit their post to insert an image" do
|
|
post_no_images.user.trust_level = TrustLevel[0]
|
|
post_no_images.save
|
|
expect {
|
|
post_no_images.revise(post_no_images.user, raw: post_two_images.raw)
|
|
post_no_images.reload
|
|
}.not_to change(post_no_images, :raw)
|
|
end
|
|
end
|
|
|
|
it "allows more images from a not-new account" do
|
|
post_two_images.user.trust_level = TrustLevel[1]
|
|
expect(post_two_images).to be_valid
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
describe "maximum attachments" do
|
|
let(:newuser) { Fabricate(:user, trust_level: TrustLevel[0]) }
|
|
let(:post_no_attachments) { Fabricate.build(:post, post_args.merge(user: newuser)) }
|
|
let(:post_one_attachment) { post_with_body('<a class="attachment" href="/uploads/default/1/2082985.txt">file.txt</a>', newuser) }
|
|
let(:post_two_attachments) { post_with_body('<a class="attachment" href="/uploads/default/2/20947092.log">errors.log</a> <a class="attachment" href="/uploads/default/3/283572385.3ds">model.3ds</a>', newuser) }
|
|
|
|
it "returns 0 attachments for an empty post" do
|
|
expect(Fabricate.build(:post).attachment_count).to eq(0)
|
|
end
|
|
|
|
it "finds attachments from HTML" do
|
|
expect(post_two_attachments.attachment_count).to eq(2)
|
|
end
|
|
|
|
context "validation" do
|
|
|
|
before do
|
|
SiteSetting.newuser_max_attachments = 1
|
|
end
|
|
|
|
context 'newuser' do
|
|
it "allows a new user to post below the limit" do
|
|
expect(post_one_attachment).to be_valid
|
|
end
|
|
|
|
it "doesn't allow more than the maximum" do
|
|
expect(post_two_attachments).not_to be_valid
|
|
end
|
|
|
|
it "doesn't allow a new user to edit their post to insert an attachment" do
|
|
post_no_attachments.user.trust_level = TrustLevel[0]
|
|
post_no_attachments.save
|
|
expect {
|
|
post_no_attachments.revise(post_no_attachments.user, raw: post_two_attachments.raw)
|
|
post_no_attachments.reload
|
|
}.not_to change(post_no_attachments, :raw)
|
|
end
|
|
end
|
|
|
|
it "allows more attachments from a not-new account" do
|
|
post_two_attachments.user.trust_level = TrustLevel[1]
|
|
expect(post_two_attachments).to be_valid
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
context "links" do
|
|
let(:newuser) { Fabricate(:user, trust_level: TrustLevel[0]) }
|
|
let(:no_links) { post_with_body("hello world my name is evil trout", newuser) }
|
|
let(:one_link) { post_with_body("[jlawr](http://www.imdb.com/name/nm2225369)", newuser) }
|
|
let(:two_links) { post_with_body("<a href='http://disneyland.disney.go.com/'>disney</a> <a href='http://reddit.com'>reddit</a>", newuser) }
|
|
let(:three_links) { post_with_body("http://discourse.org and http://discourse.org/another_url and http://www.imdb.com/name/nm2225369", newuser) }
|
|
|
|
describe "raw_links" do
|
|
it "returns a blank collection for a post with no links" do
|
|
expect(no_links.raw_links).to be_blank
|
|
end
|
|
|
|
it "finds a link within markdown" do
|
|
expect(one_link.raw_links).to eq(["http://www.imdb.com/name/nm2225369"])
|
|
end
|
|
|
|
it "can find two links from html" do
|
|
expect(two_links.raw_links).to eq(["http://disneyland.disney.go.com/", "http://reddit.com"])
|
|
end
|
|
|
|
it "can find three links without markup" do
|
|
expect(three_links.raw_links).to eq(["http://discourse.org", "http://discourse.org/another_url", "http://www.imdb.com/name/nm2225369"])
|
|
end
|
|
end
|
|
|
|
describe "linked_hosts" do
|
|
it "returns blank with no links" do
|
|
expect(no_links.linked_hosts).to be_blank
|
|
end
|
|
|
|
it "returns the host and a count for links" do
|
|
expect(two_links.linked_hosts).to eq("disneyland.disney.go.com" => 1, "reddit.com" => 1)
|
|
end
|
|
|
|
it "it counts properly with more than one link on the same host" do
|
|
expect(three_links.linked_hosts).to eq("discourse.org" => 1, "www.imdb.com" => 1)
|
|
end
|
|
end
|
|
|
|
describe "total host usage" do
|
|
|
|
it "has none for a regular post" do
|
|
expect(no_links.total_hosts_usage).to be_blank
|
|
end
|
|
|
|
context "with a previous host" do
|
|
|
|
let(:user) { old_post.newuser }
|
|
let(:another_disney_link) { post_with_body("[radiator springs](http://disneyland.disney.go.com/disney-california-adventure/radiator-springs-racers/)", newuser) }
|
|
|
|
before do
|
|
another_disney_link.save
|
|
TopicLink.extract_from(another_disney_link)
|
|
end
|
|
|
|
it "contains the new post's links, PLUS the previous one" do
|
|
expect(two_links.total_hosts_usage).to eq('disneyland.disney.go.com' => 2, 'reddit.com' => 1)
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
describe "maximums" do
|
|
let(:newuser) { Fabricate(:user, trust_level: TrustLevel[0]) }
|
|
let(:post_one_link) { post_with_body("[sherlock](http://www.bbc.co.uk/programmes/b018ttws)", newuser) }
|
|
let(:post_onebox) { post_with_body("http://www.google.com", newuser) }
|
|
let(:post_code_link) { post_with_body("<code>http://www.google.com</code>", newuser) }
|
|
let(:post_two_links) { post_with_body("<a href='http://discourse.org'>discourse</a> <a href='http://twitter.com'>twitter</a>", newuser) }
|
|
let(:post_with_mentions) { post_with_body("hello @#{newuser.username} how are you doing?", newuser) }
|
|
|
|
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 "validation" do
|
|
|
|
before do
|
|
SiteSetting.newuser_max_links = 1
|
|
end
|
|
|
|
context '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 "min_trust_to_post_links" do
|
|
it "considers oneboxes links" do
|
|
SiteSetting.min_trust_to_post_links = 3
|
|
post_onebox.user.trust_level = TrustLevel[2]
|
|
expect(post_onebox).not_to be_valid
|
|
end
|
|
|
|
it "considers links within code" do
|
|
SiteSetting.min_trust_to_post_links = 3
|
|
post_onebox.user.trust_level = TrustLevel[2]
|
|
expect(post_code_link).not_to be_valid
|
|
end
|
|
|
|
it "doesn't allow allow links if `min_trust_to_post_links` is not met" do
|
|
SiteSetting.min_trust_to_post_links = 2
|
|
post_two_links.user.trust_level = TrustLevel[1]
|
|
expect(post_one_link).not_to be_valid
|
|
end
|
|
|
|
it "will skip the check for whitelisted domains" do
|
|
SiteSetting.whitelisted_link_domains = 'www.bbc.co.uk'
|
|
SiteSetting.min_trust_to_post_links = 2
|
|
post_two_links.user.trust_level = TrustLevel[1]
|
|
expect(post_one_link).to be_valid
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
describe "@mentions" do
|
|
|
|
context '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(['jake', 'finn'])
|
|
end
|
|
|
|
it "ignores pre" do
|
|
# we need to force an inline
|
|
post = Fabricate.build(:post, post_args.merge(raw: "p <pre>@Jake</pre> @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 <pre>hello</pre> @Finn <pre></pre>"))
|
|
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(['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 "max mentions" do
|
|
|
|
let(: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) { post_with_body("@Jake @Finn are the people I'm mentioning", newuser) }
|
|
|
|
context '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 "not a new user" do
|
|
before do
|
|
SiteSetting.newuser_max_mentions_per_post = 0
|
|
SiteSetting.max_mentions_per_post = 1
|
|
end
|
|
|
|
it "allows vmax_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
|
|
|
|
context '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
|
|
|
|
context "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
|
|
|
|
context '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
|
|
|
|
describe '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
|
|
|
|
describe 'ninja 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
|
|
|
|
# ninja 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
|
|
|
|
describe 'rate limiter' do
|
|
let(:changed_by) { Fabricate(:coding_horror) }
|
|
|
|
it "triggers a rate limiter" do
|
|
EditRateLimiter.any_instance.expects(:performed!)
|
|
post.revise(changed_by, raw: 'updated body')
|
|
end
|
|
end
|
|
|
|
describe 'with a new body' do
|
|
let(:changed_by) { Fabricate(: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 'second poster posts again quickly' do
|
|
|
|
it 'is a ninja 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) { "<p><div class=\"lightbox-wrapper\"><a data-download-href=\"//localhost:3000/uploads/default/34784374092783e2fef84b8bc96d9b54c11ceea0\" href=\"//localhost:3000/uploads/default/original/1X/34784374092783e2fef84b8bc96d9b54c11ceea0.gif\" class=\"lightbox\" title=\"Sword reworks.gif\"><img src=\"//localhost:3000/uploads/default/optimized/1X/34784374092783e2fef84b8bc96d9b54c11ceea0_1_690x276.gif\" width=\"690\" height=\"276\"><div class=\"meta\">\n<span class=\"filename\">Sword reworks.gif</span><span class=\"informations\">1000x400 1000 KB</span><span class=\"expand\"></span>\n</div></a></div></p>" }
|
|
|
|
let(:post) do
|
|
Fabricate(:post,
|
|
raw: "<img src=\"/uploads/default/original/1X/34784374092783e2fef84b8bc96d9b54c11ceea0.gif\" width=\"690\" height=\"276\">",
|
|
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
|
|
|
|
describe '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
|
|
|
|
describe 'a new reply' do
|
|
|
|
let(:topic) { Fabricate(:topic) }
|
|
let(:other_user) { Fabricate(:coding_horror) }
|
|
let(:reply_text) { "[quote=\"Evil Trout, post:1\"]\nhello\n[/quote]\nHmmm!" }
|
|
let!(:post) { PostCreator.new(topic.user, raw: Fabricate.build(:post).raw, topic_id: topic.id).create }
|
|
let!(:reply) { PostCreator.new(other_user, raw: reply_text, topic_id: topic.id, reply_to_post_number: post.post_number).create }
|
|
|
|
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 '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
|
|
|
|
context '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)) }
|
|
let!(: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
|
|
|
|
context 'sort_order' do
|
|
context '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
|
|
|
|
context "reply_history" do
|
|
|
|
let!(:p1) { Fabricate(:post, post_args) }
|
|
let!(:p2) { Fabricate(:post, post_args.merge(reply_to_post_number: p1.post_number)) }
|
|
let!(:p3) { Fabricate(:post, post_args) }
|
|
let!(:p4) { Fabricate(:post, post_args.merge(reply_to_post_number: p2.post_number)) }
|
|
|
|
it "returns the posts in reply to this post" do
|
|
expect(p4.reply_history).to eq([p1, p2])
|
|
expect(p4.reply_history(1)).to eq([p2])
|
|
expect(p3.reply_history).to be_blank
|
|
expect(p2.reply_history).to eq([p1])
|
|
end
|
|
|
|
end
|
|
|
|
context "reply_ids" do
|
|
|
|
let!(:topic) { Fabricate(: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 {
|
|
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
|
|
}
|
|
|
|
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 "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) { Fabricate.build(:post, post_args.merge(raw: "please read my blog http://blog.example.com")) }
|
|
|
|
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(/nofollow noopener/)
|
|
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(/nofollow noopener/)
|
|
end
|
|
|
|
describe 'mentions' do
|
|
let(:group) do
|
|
Fabricate(:group,
|
|
mentionable_level: Group::ALIAS_LEVELS[:members_mods_and_admins]
|
|
)
|
|
end
|
|
|
|
before do
|
|
SiteSetting.queue_jobs = false
|
|
end
|
|
|
|
describe 'when user can not mention a group' do
|
|
it "should not create the mention" do
|
|
post = Fabricate(:post, raw: "hello @#{group.name}")
|
|
post.trigger_post_process
|
|
post.reload
|
|
|
|
expect(post.cooked).to eq(
|
|
%Q|<p>hello <span class="mention">@#{group.name}</span></p>|
|
|
)
|
|
end
|
|
end
|
|
|
|
describe 'when user can mention a group' do
|
|
before do
|
|
group.add(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|<p>hello <a class="mention-group" href="/groups/#{group.name}">@#{group.name}</a></p>|
|
|
)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "calculate_avg_time" do
|
|
|
|
it "should not crash" do
|
|
Post.calculate_avg_time
|
|
Post.calculate_avg_time(1.day.ago)
|
|
end
|
|
end
|
|
|
|
describe "has_host_spam" do
|
|
let(:raw) { "hello from my site http://www.example.net http://#{GlobalSetting.hostname} http://#{RailsMultisite::ConnectionManagement.current_hostname}" }
|
|
|
|
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.white_listed_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 = 1.day.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 "#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(first_baked)
|
|
expect(post.cooked).to eq(first_cooked)
|
|
expect(result).to eq(true)
|
|
end
|
|
end
|
|
|
|
describe "#set_owner" do
|
|
let(:post) { Fabricate(:post) }
|
|
let(:coding_horror) { Fabricate(:coding_horror) }
|
|
|
|
it "will change owner of a post correctly" do
|
|
post.set_owner(coding_horror, Discourse.system_user)
|
|
post.reload
|
|
|
|
expect(post.user).to eq(coding_horror)
|
|
expect(post.revisions.size).to eq(1)
|
|
end
|
|
|
|
it "skips creating new post revision if skip_revision is true" do
|
|
post.set_owner(coding_horror, Discourse.system_user, true)
|
|
post.reload
|
|
|
|
expect(post.user).to eq(coding_horror)
|
|
expect(post.revisions.size).to eq(0)
|
|
end
|
|
|
|
it "uses default locale for edit reason" do
|
|
I18n.locale = 'de'
|
|
|
|
post.set_owner(coding_horror, Discourse.system_user)
|
|
post.reload
|
|
|
|
expected_reason = I18n.with_locale(SiteSetting.default_locale) do
|
|
I18n.t('change_owner.post_revision_text')
|
|
end
|
|
|
|
expect(post.edit_reason).to eq(expected_reason)
|
|
end
|
|
end
|
|
|
|
describe ".rebake_old" do
|
|
it "will catch posts it needs to rebake" do
|
|
post = create_post
|
|
post.update_columns(baked_at: Time.new(2000, 1, 1), baked_version: -1)
|
|
Post.rebake_old(100)
|
|
|
|
post.reload
|
|
expect(post.baked_at).to be > 1.day.ago
|
|
|
|
baked = post.baked_at
|
|
Post.rebake_old(100)
|
|
post.reload
|
|
expect(post.baked_at).to eq(baked)
|
|
end
|
|
|
|
it "will rate limit globally" do
|
|
|
|
post1 = create_post
|
|
post2 = create_post
|
|
post3 = create_post
|
|
|
|
Post.where(id: [post1.id, post2.id, post3.id]).update_all(baked_version: -1)
|
|
|
|
global_setting :max_old_rebakes_per_15_minutes, 2
|
|
|
|
RateLimiter.clear_all_global!
|
|
RateLimiter.enable
|
|
|
|
Post.rebake_old(100)
|
|
|
|
expect(post3.reload.baked_version).not_to eq(-1)
|
|
expect(post2.reload.baked_version).not_to eq(-1)
|
|
expect(post1.reload.baked_version).to eq(-1)
|
|
|
|
end
|
|
end
|
|
|
|
describe ".unhide!" do
|
|
before { SiteSetting.unique_posts_mins = 5 }
|
|
|
|
it "will unhide the first post & make the topic visible" do
|
|
hidden_topic = Fabricate(:topic, visible: false)
|
|
|
|
post = create_post(topic: hidden_topic)
|
|
post.update_columns(hidden: true, hidden_at: Time.now, hidden_reason_id: 1)
|
|
post.reload
|
|
|
|
expect(post.hidden).to eq(true)
|
|
|
|
post.expects(:publish_change_to_clients!).with(:acted)
|
|
|
|
post.unhide!
|
|
|
|
post.reload
|
|
hidden_topic.reload
|
|
|
|
expect(post.hidden).to eq(false)
|
|
expect(hidden_topic.visible).to eq(true)
|
|
end
|
|
end
|
|
|
|
it "will unhide the post but will keep the topic invisible/unlisted" do
|
|
hidden_topic = Fabricate(:topic, visible: false)
|
|
create_post(topic: hidden_topic)
|
|
second_post = create_post(topic: hidden_topic)
|
|
|
|
second_post.update_columns(hidden: true, hidden_at: Time.now, hidden_reason_id: 1)
|
|
second_post.expects(:publish_change_to_clients!).with(:acted)
|
|
|
|
second_post.unhide!
|
|
|
|
second_post.reload
|
|
hidden_topic.reload
|
|
|
|
expect(second_post.hidden).to eq(false)
|
|
expect(hidden_topic.visible).to eq(false)
|
|
end
|
|
|
|
it "automatically orders post revisions by number ascending" do
|
|
post = Fabricate(:post)
|
|
post.revisions.create!(user_id: 1, post_id: post.id, number: 2)
|
|
post.revisions.create!(user_id: 1, post_id: post.id, number: 1)
|
|
expect(post.revisions.pluck(:number)).to eq([1, 2])
|
|
end
|
|
|
|
describe '#link_post_uploads' do
|
|
let(:video_upload) do
|
|
Fabricate(:upload,
|
|
url: '/uploads/default/original/1X/1/1234567890123456.mp4'
|
|
)
|
|
end
|
|
|
|
let(:image_upload) do
|
|
Fabricate(:upload,
|
|
url: '/uploads/default/original/1X/1/1234567890123456.jpg'
|
|
)
|
|
end
|
|
|
|
let(:audio_upload) do
|
|
Fabricate(:upload,
|
|
url: '/uploads/default/original/1X/1/1234567890123456.ogg'
|
|
)
|
|
end
|
|
|
|
let(:attachment_upload) do
|
|
Fabricate(:upload,
|
|
url: '/uploads/default/original/1X/1/1234567890123456.csv'
|
|
)
|
|
end
|
|
|
|
let(:raw) do
|
|
<<~RAW
|
|
<a href="#{attachment_upload.url}">Link</a>
|
|
<img src="#{image_upload.url}">
|
|
|
|
<video width="100%" height="100%" controls>
|
|
<source src="http://myforum.com#{video_upload.url}">
|
|
<a href="http://myforum.com#{video_upload.url}">http://myforum.com#{video_upload.url}</a>
|
|
</video>
|
|
|
|
<audio controls>
|
|
<source src="http://myforum.com#{audio_upload.url}">
|
|
<a href="http://myforum.com#{audio_upload.url}">http://myforum.com#{audio_upload.url}</a>
|
|
</audio>
|
|
RAW
|
|
end
|
|
|
|
let(:post) { Fabricate(:post, raw: raw) }
|
|
|
|
it "finds all the uploads in the post" do
|
|
post.custom_fields[Post::DOWNLOADED_IMAGES] = {
|
|
"/uploads/default/original/1X/1/1234567890123456.csv": attachment_upload.id
|
|
}
|
|
|
|
post.save_custom_fields
|
|
post.link_post_uploads
|
|
|
|
expect(PostUpload.where(post: post).pluck(:upload_id)).to contain_exactly(
|
|
video_upload.id, image_upload.id, audio_upload.id, attachment_upload.id
|
|
)
|
|
end
|
|
|
|
it "cleans the reverse index up for the current post" do
|
|
post.link_post_uploads
|
|
|
|
post_uploads_ids = post.post_uploads.pluck(:id)
|
|
|
|
post.link_post_uploads
|
|
|
|
expect(post.reload.post_uploads.pluck(:id)).to_not contain_exactly(
|
|
post_uploads_ids
|
|
)
|
|
end
|
|
end
|
|
|
|
end
|