discourse/spec/models/post_mover_spec.rb
Sam Saffron 4ea21fa2d0 DEV: use #frozen_string_literal: true on all spec
This change both speeds up specs (less strings to allocate) and helps catch
cases where methods in Discourse are mutating inputs.

Overall we will be migrating everything to use #frozen_string_literal: true
it will take a while, but this is the first and safest move in this direction
2019-04-30 10:27:42 +10:00

706 lines
27 KiB
Ruby

# frozen_string_literal: true
require 'rails_helper'
describe PostMover do
describe '#move_types' do
context "verify enum sequence" do
before do
@move_types = PostMover.move_types
end
it "'new_topic' should be at 1st position" do
expect(@move_types[:new_topic]).to eq(1)
end
it "'existing_topic' should be at 2nd position" do
expect(@move_types[:existing_topic]).to eq(2)
end
end
end
describe 'move_posts' do
context 'topics' do
let(:user) { Fabricate(:user, admin: true) }
let(:another_user) { Fabricate(:evil_trout) }
let(:category) { Fabricate(:category, user: user) }
let!(:topic) { Fabricate(:topic, user: user) }
let!(:p1) { Fabricate(:post, topic: topic, user: user, created_at: 3.hours.ago) }
let!(:p2) do
Fabricate(:post,
topic: topic,
user: another_user,
raw: "Has a link to [evil trout](http://eviltrout.com) which is a cool site.",
reply_to_post_number: p1.post_number)
end
let!(:p3) { Fabricate(:post, topic: topic, reply_to_post_number: p1.post_number, user: user) }
let!(:p4) { Fabricate(:post, topic: topic, reply_to_post_number: p2.post_number, user: user) }
let!(:p5) { Fabricate(:post) }
let(:p6) { Fabricate(:post, topic: topic) }
before do
SiteSetting.tagging_enabled = true
Jobs.run_immediately!
p1.replies << p3
p2.replies << p4
UserActionManager.enable
@like = PostActionCreator.like(another_user, p4)
end
context 'success' do
it "correctly handles notifications and bread crumbs" do
old_topic = p2.topic
old_topic_id = p2.topic_id
topic.move_posts(user, [p2.id, p4.id, p6.id], title: "new testing topic name")
p2.reload
expect(p2.topic_id).not_to eq(old_topic_id)
expect(p2.reply_to_post_number).to eq(nil)
expect(p2.reply_to_user_id).to eq(nil)
notification = p2.user.notifications.where(notification_type: Notification.types[:moved_post]).first
expect(notification.topic_id).to eq(p2.topic_id)
expect(notification.post_number).to eq(1)
# no message for person who made the move
expect(p4.user.notifications.where(notification_type: Notification.types[:moved_post]).length).to eq(0)
# notify at the right spot in the stream
notification = p6.user.notifications.where(notification_type: Notification.types[:moved_post]).first
expect(notification.topic_id).to eq(p2.topic_id)
# this is the 3rd post we moved
expect(notification.post_number).to eq(3)
old_topic.reload
move_message = old_topic.posts.find_by(post_number: 2)
expect(move_message.post_type).to eq(Post.types[:small_action])
expect(move_message.raw).to include("3 posts were split")
end
end
context "errors" do
it "raises an error when one of the posts doesn't exist" do
non_existent_post_id = Post.maximum(:id)&.next || 1
expect { topic.move_posts(user, [non_existent_post_id], title: "new testing topic name") }.to raise_error(Discourse::InvalidParameters)
end
it "raises an error and does not create a topic if no posts were moved" do
Topic.count.tap do |original_topic_count|
expect {
topic.move_posts(user, [], title: "new testing topic name")
}.to raise_error(Discourse::InvalidParameters)
expect(Topic.count).to eq original_topic_count
end
end
end
context "successfully moved" do
before do
TopicUser.update_last_read(user, topic.id, p4.post_number, p4.post_number, 0)
TopicLink.extract_from(p2)
end
context "post replies" do
describe "when a post with replies is moved" do
it "should update post replies correctly" do
topic.move_posts(
user,
[p2.id],
title: 'GOT is a very addictive show', category_id: category.id
)
expect(p2.reload.replies).to eq([])
end
it "doesn't raise errors with deleted replies" do
p4.trash!
topic.move_posts(
user,
[p2.id],
title: 'GOT is a very addictive show', category_id: category.id
)
expect(p2.reload.replies).to eq([])
end
end
describe "when replies of a post have been moved" do
it "should update post replies correctly" do
p5 = Fabricate(
:post,
topic: topic,
reply_to_post_number: p2.post_number,
user: another_user
)
p2.replies << p5
topic.move_posts(
user,
[p4.id],
title: 'GOT is a very addictive show', category_id: category.id
)
expect(p2.reload.replies).to eq([p5])
end
end
describe "when only one reply is left behind" do
it "should update post replies correctly" do
p5 = Fabricate(
:post,
topic: topic,
reply_to_post_number: p2.post_number,
user: another_user
)
p2.replies << p5
topic.move_posts(
user,
[p2.id, p4.id],
title: 'GOT is a very addictive show', category_id: category.id
)
expect(p2.reload.replies).to eq([p4])
end
end
end
context "to a new topic" do
it "works correctly" do
topic.expects(:add_moderator_post).once
new_topic = topic.move_posts(user, [p2.id, p4.id], title: "new testing topic name", category_id: category.id, tags: ["tag1", "tag2"])
expect(TopicUser.find_by(user_id: user.id, topic_id: topic.id).last_read_post_number).to eq(p3.post_number)
expect(new_topic).to be_present
expect(new_topic.featured_user1_id).to eq(p4.user_id)
expect(new_topic.like_count).to eq(1)
expect(new_topic.category).to eq(category)
expect(new_topic.tags.pluck(:name)).to contain_exactly("tag1", "tag2")
expect(topic.featured_user1_id).to be_blank
expect(new_topic.posts.by_post_number).to match_array([p2, p4])
new_topic.reload
expect(new_topic.posts_count).to eq(2)
expect(new_topic.highest_post_number).to eq(2)
p4.reload
expect(new_topic.last_post_user_id).to eq(p4.user_id)
expect(new_topic.last_posted_at).to eq(p4.created_at)
expect(new_topic.bumped_at).to eq(p4.created_at)
p2.reload
expect(p2.sort_order).to eq(1)
expect(p2.post_number).to eq(1)
expect(p2.topic_links.first.topic_id).to eq(new_topic.id)
expect(p4.post_number).to eq(2)
expect(p4.sort_order).to eq(2)
topic.reload
expect(topic.featured_user1_id).to be_blank
expect(topic.like_count).to eq(0)
expect(topic.posts_count).to eq(2)
expect(topic.posts.by_post_number).to match_array([p1, p3])
expect(topic.highest_post_number).to eq(p3.post_number)
# both the like and was_liked user actions should be correct
action = UserAction.find_by(user_id: another_user.id)
expect(action.target_topic_id).to eq(new_topic.id)
end
it "moving all posts will close the topic" do
topic.expects(:add_moderator_post).twice
new_topic = topic.move_posts(user, [p1.id, p2.id, p3.id, p4.id], title: "new testing topic name", category_id: category.id)
expect(new_topic).to be_present
topic.reload
expect(topic.closed).to eq(true)
end
it 'does not move posts that do not belong to the existing topic' do
new_topic = topic.move_posts(
user, [p2.id, p3.id, p5.id], title: 'Logan is a pretty good movie'
)
expect(new_topic.posts.pluck(:id).sort).to eq([p2.id, p3.id].sort)
end
it "uses default locale for moderator post" do
I18n.locale = 'de'
new_topic = topic.move_posts(user, [p2.id, p4.id], title: "new testing topic name", category_id: category.id)
post = Post.find_by(topic_id: topic.id, post_type: Post.types[:small_action])
expected_text = I18n.with_locale(:en) do
I18n.t("move_posts.new_topic_moderator_post",
count: 2,
topic_link: "[#{new_topic.title}](#{new_topic.relative_url})")
end
expect(post.raw).to eq(expected_text)
end
it "does not try to move small action posts" do
small_action = Fabricate(:post, topic: topic, raw: "A small action", post_type: Post.types[:small_action])
new_topic = topic.move_posts(user, [p2.id, p4.id, small_action.id], title: "new testing topic name", category_id: category.id)
expect(new_topic.posts_count).to eq(2)
expect(small_action.topic_id).to eq(topic.id)
moderator_post = topic.posts.last
expect(moderator_post.raw).to include("2 posts were split")
end
it "forces resulting topic owner to watch the new topic" do
new_topic = topic.move_posts(user, [p2.id, p4.id], title: "new testing topic name", category_id: category.id)
expect(new_topic.posts_count).to eq(2)
expect(TopicUser.exists?(
user_id: another_user,
topic_id: new_topic.id,
notification_level: TopicUser.notification_levels[:watching],
notifications_reason_id: TopicUser.notification_reasons[:created_topic]
)).to eq(true)
end
end
context "to an existing topic" do
let!(:destination_topic) { Fabricate(:topic, user: another_user) }
let!(:destination_op) { Fabricate(:post, topic: destination_topic, user: another_user) }
it "works correctly" do
topic.expects(:add_moderator_post).once
moved_to = topic.move_posts(user, [p2.id, p4.id], destination_topic_id: destination_topic.id)
expect(moved_to).to eq(destination_topic)
# Check out new topic
moved_to.reload
expect(moved_to.posts_count).to eq(3)
expect(moved_to.highest_post_number).to eq(3)
expect(moved_to.user_id).to eq(destination_op.user_id)
expect(moved_to.like_count).to eq(1)
expect(moved_to.category_id).to eq(SiteSetting.uncategorized_category_id)
p4.reload
expect(moved_to.last_post_user_id).to eq(p4.user_id)
expect(moved_to.last_posted_at).to eq(p4.created_at)
expect(moved_to.bumped_at).to eq(p4.created_at)
# Posts should be re-ordered
p2.reload
expect(p2.sort_order).to eq(2)
expect(p2.post_number).to eq(2)
expect(p2.topic_id).to eq(moved_to.id)
expect(p2.reply_count).to eq(1)
expect(p2.reply_to_post_number).to eq(nil)
expect(p4.post_number).to eq(3)
expect(p4.sort_order).to eq(3)
expect(p4.topic_id).to eq(moved_to.id)
expect(p4.reply_count).to eq(0)
expect(p4.reply_to_post_number).to eq(2)
# Check out the original topic
topic.reload
expect(topic.posts_count).to eq(2)
expect(topic.highest_post_number).to eq(3)
expect(topic.featured_user1_id).to be_blank
expect(topic.like_count).to eq(0)
expect(topic.posts_count).to eq(2)
expect(topic.posts.by_post_number).to match_array([p1, p3])
expect(topic.highest_post_number).to eq(p3.post_number)
# Should notify correctly
notification = p2.user.notifications.where(notification_type: Notification.types[:moved_post]).first
expect(notification.topic_id).to eq(p2.topic_id)
expect(notification.post_number).to eq(p2.post_number)
# Should update last reads
expect(TopicUser.find_by(user_id: user.id, topic_id: topic.id).last_read_post_number).to eq(p3.post_number)
end
it "moving all posts will close the topic" do
topic.expects(:add_moderator_post).twice
moved_to = topic.move_posts(user, [p1.id, p2.id, p3.id, p4.id], destination_topic_id: destination_topic.id)
expect(moved_to).to be_present
topic.reload
expect(topic.closed).to eq(true)
end
it "does not try to move small action posts" do
small_action = Fabricate(:post, topic: topic, raw: "A small action", post_type: Post.types[:small_action])
moved_to = topic.move_posts(user, [p1.id, p2.id, p3.id, p4.id, small_action.id], destination_topic_id: destination_topic.id)
moved_to.reload
expect(moved_to.posts_count).to eq(5)
expect(small_action.topic_id).to eq(topic.id)
moderator_post = topic.posts.find_by(post_number: 2)
expect(moderator_post.raw).to include("4 posts were merged")
end
end
shared_examples "moves email related stuff" do
it "moves incoming email" do
Fabricate(:incoming_email, user: old_post.user, topic: old_post.topic, post: old_post)
new_topic = topic.move_posts(user, [old_post.id], title: "new testing topic name")
new_post = new_topic.first_post
email = new_post.incoming_email
expect(email).to be_present
expect(email.topic_id).to eq(new_topic.id)
expect(email.post_id).to eq(new_post.id)
expect(old_post.reload.incoming_email).to_not be_present unless old_post.id == new_post.id
end
it "moves email log entries" do
old_topic = old_post.topic
2.times do
Fabricate(:email_log,
user: old_post.user,
post: old_post,
email_type: :mailing_list
)
end
some_post = Fabricate(:post)
Fabricate(:email_log,
user: some_post.user,
post: some_post,
email_type: :mailing_list
)
expect(EmailLog.where(post_id: old_post.id).count).to eq(2)
new_topic = old_topic.move_posts(
user,
[old_post.id],
title: "new testing topic name"
)
new_post = new_topic.first_post
expect(EmailLog.where(post_id: new_post.id).count).to eq(2)
end
it "preserves post attributes" do
old_post.update_columns(cook_method: Post.cook_methods[:email], via_email: true, raw_email: "raw email content")
new_topic = old_post.topic.move_posts(user, [old_post.id], title: "new testing topic name")
new_post = new_topic.first_post
expect(new_post.cook_method).to eq(Post.cook_methods[:email])
expect(new_post.via_email).to eq(true)
expect(new_post.raw_email).to eq("raw email content")
end
end
context "moving the first post" do
it "copies the OP, doesn't delete it" do
topic.expects(:add_moderator_post).once
new_topic = topic.move_posts(user, [p1.id, p2.id], title: "new testing topic name")
expect(new_topic).to be_present
expect(new_topic.posts.by_post_number.first.raw).to eq(p1.raw)
expect(new_topic.posts_count).to eq(2)
expect(new_topic.highest_post_number).to eq(2)
# First post didn't move
p1.reload
expect(p1.sort_order).to eq(1)
expect(p1.post_number).to eq(1)
expect(p1.topic_id).to eq(topic.id)
expect(p1.reply_count).to eq(0)
# New first post
new_first = new_topic.posts.where(post_number: 1).first
expect(new_first.reply_count).to eq(1)
expect(new_first.created_at).to be_within(1.second).of(p1.created_at)
# Second post is in a new topic
p2.reload
expect(p2.post_number).to eq(2)
expect(p2.sort_order).to eq(2)
expect(p2.topic_id).to eq(new_topic.id)
expect(p2.reply_to_post_number).to eq(1)
expect(p2.reply_count).to eq(0)
topic.reload
expect(topic.posts.by_post_number).to match_array([p1, p3, p4])
expect(topic.highest_post_number).to eq(p4.post_number)
end
it "preserves post actions in the new post" do
PostActionCreator.like(another_user, p1)
new_topic = topic.move_posts(user, [p1.id], title: "new testing topic name")
new_post = new_topic.posts.where(post_number: 1).first
expect(new_topic.like_count).to eq(1)
expect(new_post.like_count).to eq(1)
expect(new_post.post_actions.size).to eq(1)
end
it "preserves the custom_fields in the new post" do
custom_fields = { "some_field" => 'payload' }
p1.custom_fields = custom_fields
p1.save_custom_fields
new_topic = topic.move_posts(user, [p1.id], title: "new testing topic name")
expect(new_topic.first_post.custom_fields).to eq(custom_fields)
end
include_examples "moves email related stuff" do
let!(:old_post) { p1 }
end
end
context "moving replies" do
include_examples "moves email related stuff" do
let!(:old_post) { p3 }
end
end
context "to an existing topic with a deleted post" do
before do
topic.expects(:add_moderator_post)
end
let!(:destination_topic) { Fabricate(:topic, user: user) }
let!(:destination_op) { Fabricate(:post, topic: destination_topic, user: user) }
let!(:destination_deleted_reply) { Fabricate(:post, topic: destination_topic, user: another_user) }
let(:moved_to) { topic.move_posts(user, [p2.id, p4.id], destination_topic_id: destination_topic.id) }
it "works correctly" do
destination_deleted_reply.trash!
expect(moved_to).to eq(destination_topic)
# Check out new topic
moved_to.reload
expect(moved_to.posts_count).to eq(3)
expect(moved_to.highest_post_number).to eq(4)
# Posts should be re-ordered
p2.reload
expect(p2.sort_order).to eq(3)
expect(p2.post_number).to eq(3)
expect(p2.topic_id).to eq(moved_to.id)
expect(p2.reply_count).to eq(1)
expect(p2.reply_to_post_number).to eq(nil)
p4.reload
expect(p4.post_number).to eq(4)
expect(p4.sort_order).to eq(4)
expect(p4.topic_id).to eq(moved_to.id)
expect(p4.reply_to_post_number).to eq(p2.post_number)
end
end
context "to an existing closed topic" do
let!(:destination_topic) { Fabricate(:topic, closed: true) }
it "works correctly for admin" do
admin = Fabricate(:admin)
moved_to = topic.move_posts(admin, [p1.id, p2.id], destination_topic_id: destination_topic.id)
expect(moved_to).to be_present
moved_to.reload
expect(moved_to.posts_count).to eq(2)
expect(moved_to.highest_post_number).to eq(2)
end
end
it "skips validations when moving posts" do
p1.update_attribute(:raw, "foo")
p2.update_attribute(:raw, "bar")
new_topic = topic.move_posts(user, [p1.id, p2.id], title: "new testing topic name")
expect(new_topic).to be_present
expect(new_topic.posts.by_post_number.first.raw).to eq(p1.raw)
expect(new_topic.posts.by_post_number.last.raw).to eq(p2.raw)
expect(new_topic.posts_count).to eq(2)
end
end
end
context 'messages' do
let(:user) { Fabricate(:user) }
let(:admin) { Fabricate(:admin) }
let(:evil_trout) { Fabricate(:evil_trout) }
let(:another_user) { Fabricate(:user) }
let(:regular_user) { Fabricate(:trust_level_4) }
let(:topic) { Fabricate(:topic) }
let(:personal_message) { Fabricate(:private_message_topic, user: evil_trout) }
let!(:p1) { Fabricate(:post, topic: personal_message, user: user) }
let!(:p2) { Fabricate(:post, topic: personal_message, reply_to_post_number: p1.post_number, user: another_user) }
let!(:p3) { Fabricate(:post, topic: personal_message, reply_to_post_number: p1.post_number, user: user) }
let!(:p4) { Fabricate(:post, topic: personal_message, reply_to_post_number: p2.post_number, user: user) }
let!(:p5) { Fabricate(:post, topic: personal_message, user: evil_trout) }
let(:another_personal_message) do
Fabricate(:private_message_topic, user: user, topic_allowed_users: [
Fabricate.build(:topic_allowed_user, user: admin)
])
end
let!(:p6) { Fabricate(:post, topic: another_personal_message, user: evil_trout) }
before do
SiteSetting.tagging_enabled = true
Jobs.run_immediately!
p1.replies << p3
p2.replies << p4
UserActionManager.enable
@like = PostActionCreator.like(another_user, p4)
end
context 'move to new message' do
it "adds post users as topic allowed users" do
personal_message.move_posts(admin, [p2.id, p3.id, p4.id, p5.id], title: "new testing message name", tags: ["tag1", "tag2"], archetype: "private_message")
p2.reload
destination_topic = p2.topic
expect(destination_topic.archetype).to eq(Archetype.private_message)
expect(destination_topic.topic_allowed_users.where(user_id: user.id).count).to eq(1)
expect(destination_topic.topic_allowed_users.where(user_id: another_user.id).count).to eq(1)
expect(destination_topic.topic_allowed_users.where(user_id: evil_trout.id).count).to eq(1)
expect(destination_topic.tags.pluck(:name)).to eq([])
end
it "can add tags to new message when allow_staff_to_tag_pms is enabled" do
SiteSetting.allow_staff_to_tag_pms = true
personal_message.move_posts(admin, [p2.id, p5.id], title: "new testing message name", tags: ["tag1", "tag2"], archetype: "private_message")
p2.reload
expect(p2.topic.tags.pluck(:name)).to contain_exactly("tag1", "tag2")
end
it "correctly handles notifications" do
old_message = p2.topic
old_message_id = p2.topic_id
personal_message.move_posts(admin, [p2.id, p4.id], title: "new testing message name", archetype: "private_message")
p2.reload
expect(p2.topic_id).not_to eq(old_message_id)
expect(p2.reply_to_post_number).to eq(nil)
expect(p2.reply_to_user_id).to eq(nil)
notification = p2.user.notifications.where(notification_type: Notification.types[:moved_post]).first
expect(notification.topic_id).to eq(p2.topic_id)
expect(notification.post_number).to eq(1)
# no message for person who made the move
expect(admin.notifications.where(notification_type: Notification.types[:moved_post]).length).to eq(0)
old_message.reload
move_message = old_message.posts.find_by(post_number: 2)
expect(move_message.post_type).to eq(Post.types[:whisper])
expect(move_message.raw).to include("2 posts were split")
end
end
context 'move to existing message' do
it "adds post users as topic allowed users" do
personal_message.move_posts(admin, [p2.id, p5.id], destination_topic_id: another_personal_message.id, archetype: "private_message")
p2.reload
expect(p2.topic_id).to eq(another_personal_message.id)
another_personal_message.reload
expect(another_personal_message.topic_allowed_users.where(user_id: another_user.id).count).to eq(1)
expect(another_personal_message.topic_allowed_users.where(user_id: evil_trout.id).count).to eq(1)
end
it "can add additional participants" do
personal_message.move_posts(admin, [p2.id, p5.id], destination_topic_id: another_personal_message.id, participants: [regular_user.username], archetype: "private_message")
another_personal_message.reload
expect(another_personal_message.topic_allowed_users.where(user_id: another_user.id).count).to eq(1)
expect(another_personal_message.topic_allowed_users.where(user_id: evil_trout.id).count).to eq(1)
expect(another_personal_message.topic_allowed_users.where(user_id: regular_user.id).count).to eq(1)
end
it "does not allow moving regular topic posts in personal message" do
expect {
personal_message.move_posts(admin, [p2.id, p5.id], destination_topic_id: topic.id)
}.to raise_error(Discourse::InvalidParameters)
end
it "moving all posts will close the message" do
moved_to = personal_message.move_posts(admin, [p1.id, p2.id, p3.id, p4.id, p5.id], destination_topic_id: another_personal_message.id, archetype: "private_message")
expect(moved_to).to be_present
personal_message.reload
expect(personal_message.closed).to eq(true)
expect(moved_to.posts_count).to eq(6)
end
it "uses the correct small action post" do
moved_to = personal_message.move_posts(admin, [p2.id], destination_topic_id: another_personal_message.id, archetype: "private_message")
post = Post.find_by(topic_id: personal_message.id, post_type: Post.types[:whisper])
expected_text = I18n.t(
"move_posts.existing_message_moderator_post",
count: 1,
topic_link: "[#{moved_to.title}](#{p2.reload.url})",
locale: :en
)
expect(post.raw).to eq(expected_text)
end
end
end
context 'banner topic' do
let(:admin) { Fabricate(:admin) }
let(:evil_trout) { Fabricate(:evil_trout) }
let(:regular_user) { Fabricate(:trust_level_4) }
let(:topic) { Fabricate(:topic) }
let(:personal_message) { Fabricate(:private_message_topic, user: regular_user) }
let(:banner_topic) { Fabricate(:banner_topic, user: evil_trout) }
let!(:p1) { Fabricate(:post, topic: banner_topic, user: evil_trout) }
let!(:p2) { Fabricate(:post, topic: banner_topic, reply_to_post_number: p1.post_number, user: regular_user) }
context 'move to existing topic' do
it "allows moving banner topic posts in regular topic" do
banner_topic.move_posts(admin, [p2.id], destination_topic_id: topic.id)
expect(p2.reload.topic_id).to eq(topic.id)
end
it "does not allow moving banner topic posts in personal message" do
expect {
banner_topic.move_posts(admin, [p2.id], destination_topic_id: personal_message.id)
}.to raise_error(Discourse::InvalidParameters)
end
end
end
end
end