# frozen_string_literal: true require "rails_helper" describe Chat::MessageMover do fab!(:acting_user) { Fabricate(:admin, username: "testmovechat") } fab!(:source_channel) { Fabricate(:category_channel) } fab!(:destination_channel) { Fabricate(:category_channel) } fab!(:message1) do Fabricate( :chat_message, chat_channel: source_channel, created_at: 3.minutes.ago, message: "the first to be moved", ) end fab!(:message2) do Fabricate( :chat_message, chat_channel: source_channel, created_at: 2.minutes.ago, message: "message deux @testmovechat", ) end fab!(:message3) do Fabricate( :chat_message, chat_channel: source_channel, created_at: 1.minute.ago, message: "the third message", ) end fab!(:message4) { Fabricate(:chat_message, chat_channel: destination_channel) } fab!(:message5) { Fabricate(:chat_message, chat_channel: destination_channel) } fab!(:message6) { Fabricate(:chat_message, chat_channel: destination_channel) } let(:move_message_ids) { [message1.id, message2.id, message3.id] } describe "#move_to_channel" do def move!(move_message_ids = [message1.id, message2.id, message3.id]) described_class.new( acting_user: acting_user, source_channel: source_channel, message_ids: move_message_ids, ).move_to_channel(destination_channel) end it "raises an error if either the source or destination channels are not public (they cannot be DM channels)" do expect { described_class.new( acting_user: acting_user, source_channel: Fabricate(:direct_message_channel), message_ids: move_message_ids, ).move_to_channel(destination_channel) }.to raise_error(Chat::MessageMover::InvalidChannel) expect { described_class.new( acting_user: acting_user, source_channel: source_channel, message_ids: move_message_ids, ).move_to_channel(Fabricate(:direct_message_channel)) }.to raise_error(Chat::MessageMover::InvalidChannel) end it "raises an error if no messages are found using the message ids" do other_channel = Fabricate(:chat_channel) message1.update(chat_channel: other_channel) message2.update(chat_channel: other_channel) message3.update(chat_channel: other_channel) expect { move! }.to raise_error(Chat::MessageMover::NoMessagesFound) end it "deletes the messages from the source channel and sends messagebus delete messages" do messages = MessageBus.track_publish { move! } expect(ChatMessage.where(id: move_message_ids)).to eq([]) deleted_messages = ChatMessage.with_deleted.where(id: move_message_ids).order(:id) expect(deleted_messages.count).to eq(3) expect(messages.first.channel).to eq("/chat/#{source_channel.id}") expect(messages.first.data[:typ]).to eq("bulk_delete") expect(messages.first.data[:deleted_ids]).to eq(deleted_messages.map(&:id)) expect(messages.first.data[:deleted_at]).not_to eq(nil) end it "creates a message in the source channel to indicate that the messages have been moved" do move! placeholder_message = ChatMessage.where(chat_channel: source_channel).order(:created_at).last destination_first_moved_message = ChatMessage.find_by(chat_channel: destination_channel, message: "the first to be moved") expect(placeholder_message.message).to eq( I18n.t( "chat.channel.messages_moved", count: move_message_ids.length, acting_username: acting_user.username, channel_name: destination_channel.title(acting_user), first_moved_message_url: destination_first_moved_message.url, ), ) end it "preserves the order of the messages in the destination channel" do move! moved_messages = ChatMessage.where(chat_channel: destination_channel).order("created_at ASC, id ASC").last(3) expect(moved_messages.map(&:message)).to eq( ["the first to be moved", "message deux @testmovechat", "the third message"], ) end it "updates references for reactions, uploads, revisions, mentions, etc." do reaction = Fabricate(:chat_message_reaction, chat_message: message1) upload = Fabricate(:upload_reference, target: message1) notification = Fabricate(:notification) mention = Fabricate( :chat_mention, chat_message: message2, user: acting_user, notification: notification, ) revision = Fabricate(:chat_message_revision, chat_message: message3) webhook_event = Fabricate(:chat_webhook_event, chat_message: message3) move! moved_messages = ChatMessage.where(chat_channel: destination_channel).order("created_at ASC, id ASC").last(3) expect(reaction.reload.chat_message_id).to eq(moved_messages.first.id) expect(upload.reload.target_id).to eq(moved_messages.first.id) expect(mention.reload.chat_message_id).to eq(moved_messages.second.id) expect(revision.reload.chat_message_id).to eq(moved_messages.third.id) expect(webhook_event.reload.chat_message_id).to eq(moved_messages.third.id) end it "does not preserve reply chains using in_reply_to_id" do message3.update!(in_reply_to: message2) message2.update!(in_reply_to: message1) move! moved_messages = ChatMessage.where(chat_channel: destination_channel).order("created_at ASC, id ASC").last(3) expect(moved_messages.pluck(:in_reply_to_id).uniq).to eq([nil]) end it "clears in_reply_to_id for remaining messages when the messages they were replying to are moved" do message3.update!(in_reply_to: message2) message2.update!(in_reply_to: message1) move!([message2.id]) expect(message3.reload.in_reply_to_id).to eq(nil) end context "when there is a thread" do fab!(:thread) { Fabricate(:chat_thread, channel: source_channel, original_message: message1) } before do message1.update!(thread: thread) message2.update!(thread: thread) message3.update!(thread: thread) end it "does not preserve thread_ids" do move! moved_messages = ChatMessage .where(chat_channel: destination_channel) .order("created_at ASC, id ASC") .last(3) expect(moved_messages.pluck(:thread_id).uniq).to eq([nil]) end it "deletes the empty thread" do move! expect(ChatThread.exists?(id: thread.id)).to eq(false) end it "clears in_reply_to_id for remaining messages when the messages they were replying to are moved but leaves the thread_id" do message3.update!(in_reply_to: message2) message2.update!(in_reply_to: message1) move!([message2.id]) expect(message3.reload.in_reply_to_id).to eq(nil) expect(message3.reload.thread).to eq(thread) end context "when a thread original message is moved" do it "creates a new thread for the messages left behind in the old channel" do message4 = Fabricate( :chat_message, chat_channel: source_channel, message: "the fourth message", in_reply_to: message3, thread: thread, ) message5 = Fabricate( :chat_message, chat_channel: source_channel, message: "the fifth message", thread: thread, ) expect { move! }.to change { ChatThread.count }.by(1) new_thread = ChatThread.last expect(message4.reload.thread_id).to eq(new_thread.id) expect(message5.reload.thread_id).to eq(new_thread.id) expect(new_thread.channel).to eq(source_channel) expect(new_thread.original_message).to eq(message4) end end context "when multiple thread original messages are moved" do it "works the same as when one is" do message4 = Fabricate(:chat_message, chat_channel: source_channel, message: "the fourth message") message5 = Fabricate( :chat_message, chat_channel: source_channel, in_reply_to: message5, message: "the fifth message", ) other_thread = Fabricate(:chat_thread, channel: source_channel, original_message: message4) message4.update!(thread: other_thread) message5.update!(thread: other_thread) expect { move!([message1.id, message4.id]) }.to change { ChatThread.count }.by(2) new_threads = ChatThread.order(:created_at).last(2) expect(message3.reload.thread_id).to eq(new_threads.first.id) expect(message5.reload.thread_id).to eq(new_threads.second.id) expect(new_threads.first.channel).to eq(source_channel) expect(new_threads.second.channel).to eq(source_channel) expect(new_threads.first.original_message).to eq(message2) expect(new_threads.second.original_message).to eq(message5) end end end end end