994 lines
34 KiB
Ruby
994 lines
34 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
RSpec.describe Chat::UpdateMessage do
|
|
describe described_class::Contract, type: :model do
|
|
subject(:contract) { described_class.new(upload_ids: upload_ids) }
|
|
|
|
let(:upload_ids) { nil }
|
|
|
|
it { is_expected.to validate_presence_of :message_id }
|
|
|
|
context "when uploads are not provided" do
|
|
it { is_expected.to validate_presence_of :message }
|
|
end
|
|
|
|
context "when uploads are provided" do
|
|
let(:upload_ids) { "2,3" }
|
|
|
|
it { is_expected.not_to validate_presence_of :message }
|
|
end
|
|
end
|
|
|
|
describe "with validation" do
|
|
let(:guardian) { Guardian.new(user1) }
|
|
fab!(:admin1) { Fabricate(:admin) }
|
|
fab!(:admin2) { Fabricate(:admin) }
|
|
fab!(:user1) { Fabricate(:user, refresh_auto_groups: true) }
|
|
fab!(:user2) { Fabricate(:user) }
|
|
fab!(:user3) { Fabricate(:user) }
|
|
fab!(:user4) { Fabricate(:user) }
|
|
fab!(:admin_group) do
|
|
Fabricate(
|
|
:public_group,
|
|
users: [admin1, admin2],
|
|
mentionable_level: Group::ALIAS_LEVELS[:everyone],
|
|
)
|
|
end
|
|
fab!(:user_without_memberships) { Fabricate(:user) }
|
|
fab!(:public_chat_channel) { Fabricate(:category_channel) }
|
|
|
|
before do
|
|
SiteSetting.chat_enabled = true
|
|
SiteSetting.chat_allowed_groups = Group::AUTO_GROUPS[:everyone]
|
|
SiteSetting.chat_duplicate_message_sensitivity = 0
|
|
Jobs.run_immediately!
|
|
|
|
[admin1, admin2, user1, user2, user3, user4].each { |user| public_chat_channel.add(user) }
|
|
end
|
|
|
|
def create_chat_message(user, message, channel, upload_ids: nil)
|
|
Fabricate(
|
|
:chat_message,
|
|
chat_channel: channel,
|
|
user: user,
|
|
message: message,
|
|
upload_ids: upload_ids,
|
|
use_service: true,
|
|
)
|
|
end
|
|
|
|
it "errors when length is less than `chat_minimum_message_length`" do
|
|
SiteSetting.chat_minimum_message_length = 10
|
|
og_message = "This won't be changed!"
|
|
chat_message = create_chat_message(user1, og_message, public_chat_channel)
|
|
new_message = "2 short"
|
|
|
|
expect do
|
|
described_class.call(guardian: guardian, message_id: chat_message.id, message: new_message)
|
|
end.to raise_error(ActiveRecord::RecordInvalid).with_message(
|
|
"Validation failed: " +
|
|
I18n.t(
|
|
"chat.errors.minimum_length_not_met",
|
|
{ count: SiteSetting.chat_minimum_message_length },
|
|
),
|
|
)
|
|
|
|
expect(chat_message.reload.message).to eq(og_message)
|
|
end
|
|
|
|
it "errors when length is greater than `chat_maximum_message_length`" do
|
|
SiteSetting.chat_maximum_message_length = 100
|
|
og_message = "This won't be changed!"
|
|
chat_message = create_chat_message(user1, og_message, public_chat_channel)
|
|
new_message = "2 long" * 100
|
|
|
|
expect do
|
|
described_class.call(guardian: guardian, message_id: chat_message.id, message: new_message)
|
|
end.to raise_error(ActiveRecord::RecordInvalid).with_message(
|
|
"Validation failed: " +
|
|
I18n.t(
|
|
"chat.errors.message_too_long",
|
|
{ count: SiteSetting.chat_maximum_message_length },
|
|
),
|
|
)
|
|
|
|
expect(chat_message.reload.message).to eq(og_message)
|
|
end
|
|
|
|
it "errors when a blank message is sent" do
|
|
og_message = "This won't be changed!"
|
|
chat_message = create_chat_message(user1, og_message, public_chat_channel)
|
|
new_message = " "
|
|
|
|
updater =
|
|
described_class.call(guardian: guardian, message_id: chat_message.id, message: new_message)
|
|
|
|
expect(updater.contract).not_to be_valid
|
|
expect(updater.contract.errors.added?(:message, :blank)).to be_truthy
|
|
expect(chat_message.reload.message).to eq(og_message)
|
|
end
|
|
|
|
it "errors if a user other than the message user is trying to edit the message" do
|
|
og_message = "This won't be changed!"
|
|
chat_message = create_chat_message(user1, og_message, public_chat_channel)
|
|
new_message = "2 short"
|
|
updater =
|
|
described_class.call(
|
|
guardian: Guardian.new(Fabricate(:user)),
|
|
message_id: chat_message.id,
|
|
message: new_message,
|
|
)
|
|
|
|
expect(updater.message.reload.message).not_to eq(new_message)
|
|
end
|
|
|
|
it "updates a message's content" do
|
|
chat_message = create_chat_message(user1, "This will be changed", public_chat_channel)
|
|
new_message = "Change to this!"
|
|
|
|
described_class.call(guardian: guardian, message_id: chat_message.id, message: new_message)
|
|
expect(chat_message.reload.message).to eq(new_message)
|
|
end
|
|
|
|
it "cleans message's content" do
|
|
chat_message = create_chat_message(user1, "This will be changed", public_chat_channel)
|
|
new_message = "bbbbb\n"
|
|
|
|
described_class.call(guardian: guardian, message_id: chat_message.id, message: new_message)
|
|
expect(chat_message.reload.message).to eq("bbbbb")
|
|
end
|
|
|
|
context "when strip_whitespaces is disabled" do
|
|
it "doesn't remove new lines" do
|
|
chat_message = create_chat_message(user1, "This will be changed", public_chat_channel)
|
|
new_message = "bbbbb\n"
|
|
|
|
described_class.call(
|
|
guardian: guardian,
|
|
message_id: chat_message.id,
|
|
message: new_message,
|
|
strip_whitespaces: false,
|
|
)
|
|
expect(chat_message.reload.message).to eq("bbbbb\n")
|
|
end
|
|
end
|
|
|
|
it "cooks the message" do
|
|
chat_message = create_chat_message(user1, "This will be changed", public_chat_channel)
|
|
new_message = "Change **to** this!"
|
|
|
|
described_class.call(guardian: guardian, message_id: chat_message.id, message: new_message)
|
|
expect(chat_message.reload.cooked).to eq("<p>Change <strong>to</strong> this!</p>")
|
|
end
|
|
|
|
it "updates the excerpt" do
|
|
chat_message = create_chat_message(user1, "This is a message", public_chat_channel)
|
|
|
|
described_class.call(
|
|
guardian: guardian,
|
|
message_id: chat_message.id,
|
|
message: "Change to this!",
|
|
)
|
|
expect(chat_message.reload.excerpt).to eq("Change to this!")
|
|
end
|
|
|
|
it "publishes a DiscourseEvent for updated messages" do
|
|
chat_message = create_chat_message(user1, "This will be changed", public_chat_channel)
|
|
events =
|
|
DiscourseEvent.track_events do
|
|
described_class.call(
|
|
guardian: guardian,
|
|
message_id: chat_message.id,
|
|
message: "Change to this!",
|
|
)
|
|
end
|
|
expect(events.map { _1[:event_name] }).to include(:chat_message_edited)
|
|
end
|
|
|
|
it "publishes updated message to message bus" do
|
|
chat_message = create_chat_message(user1, "This will be changed", public_chat_channel)
|
|
new_content = "New content"
|
|
|
|
processed_message =
|
|
MessageBus
|
|
.track_publish("/chat/#{public_chat_channel.id}") do
|
|
described_class.call(
|
|
guardian: guardian,
|
|
message_id: chat_message.id,
|
|
message: new_content,
|
|
)
|
|
end
|
|
.detect { |m| m.data["type"] == "edit" }
|
|
.data
|
|
|
|
expect(processed_message["chat_message"]["message"]).to eq(new_content)
|
|
end
|
|
|
|
context "with mentions" do
|
|
it "sends notifications if a message was updated with new mentions" do
|
|
message = create_chat_message(user1, "Mentioning @#{user2.username}", public_chat_channel)
|
|
|
|
described_class.call(
|
|
guardian: guardian,
|
|
message_id: message.id,
|
|
message: "Mentioning @#{user2.username} and @#{user3.username}",
|
|
)
|
|
|
|
mention = user3.chat_mentions.where(chat_message: message.id).first
|
|
expect(mention.notifications.length).to be(1)
|
|
end
|
|
|
|
it "doesn't create mentions for already mentioned users" do
|
|
message = "ping @#{user2.username} @#{user3.username}"
|
|
chat_message = create_chat_message(user1, message, public_chat_channel)
|
|
expect {
|
|
described_class.call(
|
|
guardian: guardian,
|
|
message_id: chat_message.id,
|
|
message: message + " editedddd",
|
|
)
|
|
}.not_to change { Chat::Mention.count }
|
|
end
|
|
|
|
it "doesn't create mention notification for users without access" do
|
|
message = "ping"
|
|
chat_message = create_chat_message(user1, message, public_chat_channel)
|
|
|
|
described_class.call(
|
|
guardian: guardian,
|
|
message_id: chat_message.id,
|
|
message: message + " @#{user_without_memberships.username}",
|
|
)
|
|
|
|
mention = user_without_memberships.chat_mentions.where(chat_message: chat_message).first
|
|
expect(mention.notifications).to be_empty
|
|
end
|
|
|
|
it "destroys mentions that should be removed" do
|
|
chat_message =
|
|
create_chat_message(
|
|
user1,
|
|
"ping @#{user2.username} @#{user3.username}",
|
|
public_chat_channel,
|
|
)
|
|
expect {
|
|
described_class.call(
|
|
guardian: guardian,
|
|
message_id: chat_message.id,
|
|
message: "ping @#{user3.username}",
|
|
)
|
|
}.to change { user2.chat_mentions.count }.by(-1).and not_change {
|
|
user3.chat_mentions.count
|
|
}
|
|
end
|
|
|
|
it "creates new, leaves existing, and removes old mentions all at once" do
|
|
chat_message =
|
|
create_chat_message(
|
|
user1,
|
|
"ping @#{user2.username} @#{user3.username}",
|
|
public_chat_channel,
|
|
)
|
|
described_class.call(
|
|
guardian: guardian,
|
|
message_id: chat_message.id,
|
|
message: "ping @#{user3.username} @#{user4.username}",
|
|
)
|
|
|
|
expect(user2.chat_mentions.where(chat_message: chat_message)).not_to be_present
|
|
expect(user3.chat_mentions.where(chat_message: chat_message)).to be_present
|
|
expect(user4.chat_mentions.where(chat_message: chat_message)).to be_present
|
|
end
|
|
|
|
it "doesn't create mention notification in direct message for users without access" do
|
|
result =
|
|
Chat::CreateDirectMessageChannel.call(
|
|
guardian: user1.guardian,
|
|
target_usernames: [user1.username, user2.username],
|
|
)
|
|
service_failed!(result) if result.failure?
|
|
direct_message_channel = result.channel
|
|
message = create_chat_message(user1, "ping nobody", direct_message_channel)
|
|
|
|
described_class.call(
|
|
guardian: guardian,
|
|
message_id: message.id,
|
|
message: "ping @#{admin1.username}",
|
|
)
|
|
|
|
mention = admin1.chat_mentions.where(chat_message_id: message.id).first
|
|
expect(mention.notifications).to be_empty
|
|
end
|
|
|
|
it "creates a chat_mention record without notification when self mentioning" do
|
|
chat_message = create_chat_message(user1, "I will mention myself soon", public_chat_channel)
|
|
new_content = "hello @#{user1.username}"
|
|
|
|
described_class.call(guardian: guardian, message_id: chat_message.id, message: new_content)
|
|
|
|
mention = user1.chat_mentions.where(chat_message: chat_message).first
|
|
expect(mention).to be_present
|
|
expect(mention.notifications).to be_empty
|
|
end
|
|
|
|
it "adds mentioned user and their status to the message bus message" do
|
|
SiteSetting.enable_user_status = true
|
|
status = { description: "dentist", emoji: "tooth" }
|
|
user2.set_status!(status[:description], status[:emoji])
|
|
chat_message = create_chat_message(user1, "This will be updated", public_chat_channel)
|
|
new_content = "Hey @#{user2.username}"
|
|
|
|
processed_message =
|
|
MessageBus
|
|
.track_publish("/chat/#{public_chat_channel.id}") do
|
|
described_class.call(
|
|
guardian: guardian,
|
|
message_id: chat_message.id,
|
|
message: new_content,
|
|
)
|
|
end
|
|
.detect { |m| m.data["type"] == "processed" }
|
|
.data
|
|
|
|
expect(processed_message["chat_message"]["mentioned_users"].count).to eq(1)
|
|
|
|
mentioned_user = processed_message["chat_message"]["mentioned_users"][0]
|
|
expect(mentioned_user["id"]).to eq(user2.id)
|
|
expect(mentioned_user["username"]).to eq(user2.username)
|
|
expect(mentioned_user["status"]).to be_present
|
|
expect(mentioned_user["status"].symbolize_keys.slice(:description, :emoji)).to eq(status)
|
|
end
|
|
|
|
it "doesn't add mentioned user's status to the message bus message when status is disabled" do
|
|
SiteSetting.enable_user_status = false
|
|
user2.set_status!("dentist", "tooth")
|
|
chat_message = create_chat_message(user1, "This will be updated", public_chat_channel)
|
|
|
|
processed_message =
|
|
MessageBus
|
|
.track_publish("/chat/#{public_chat_channel.id}") do
|
|
described_class.call(
|
|
guardian: guardian,
|
|
message_id: chat_message.id,
|
|
message: "Hey @#{user2.username}",
|
|
)
|
|
end
|
|
.detect { |m| m.data["type"] == "processed" }
|
|
.data
|
|
|
|
expect(processed_message["chat_message"]["mentioned_users"].count).to be(1)
|
|
mentioned_user = processed_message["chat_message"]["mentioned_users"][0]
|
|
|
|
expect(mentioned_user["status"]).to be_blank
|
|
end
|
|
|
|
context "when updating a mentioned user" do
|
|
it "updates the mention record" do
|
|
chat_message = create_chat_message(user1, "ping @#{user2.username}", public_chat_channel)
|
|
|
|
described_class.call(
|
|
guardian: guardian,
|
|
message_id: chat_message.id,
|
|
message: "ping @#{user3.username}",
|
|
)
|
|
|
|
user2_mentions = user2.chat_mentions.where(chat_message: chat_message)
|
|
expect(user2_mentions.length).to be(0)
|
|
|
|
user3_mentions = user3.chat_mentions.where(chat_message: chat_message)
|
|
expect(user3_mentions.length).to be(1)
|
|
end
|
|
end
|
|
|
|
context "when there are duplicate mentions" do
|
|
it "creates a single mention record per user" do
|
|
chat_message = create_chat_message(user1, "ping @#{user2.username}", public_chat_channel)
|
|
|
|
described_class.call(
|
|
guardian: guardian,
|
|
message_id: chat_message.id,
|
|
message: "ping @#{user2.username} @#{user2.username} edited",
|
|
)
|
|
|
|
expect(user2.chat_mentions.where(chat_message: chat_message).count).to eq(1)
|
|
end
|
|
end
|
|
|
|
describe "with group mentions" do
|
|
fab!(:group_1) do
|
|
Fabricate(
|
|
:public_group,
|
|
users: [user1, user2],
|
|
mentionable_level: Group::ALIAS_LEVELS[:everyone],
|
|
)
|
|
end
|
|
fab!(:group_2) do
|
|
Fabricate(
|
|
:public_group,
|
|
users: [user3, user4],
|
|
mentionable_level: Group::ALIAS_LEVELS[:everyone],
|
|
)
|
|
end
|
|
|
|
it "creates a mention record when a group was mentioned on message update" do
|
|
chat_message = create_chat_message(user1, "ping nobody", public_chat_channel)
|
|
|
|
described_class.call(
|
|
guardian: guardian,
|
|
message_id: chat_message.id,
|
|
message: "ping @#{group_1.name}",
|
|
)
|
|
|
|
expect(group_1.chat_mentions.where(chat_message: chat_message).count).to be(1)
|
|
end
|
|
|
|
it "updates mention records when another group was mentioned on message update" do
|
|
chat_message = create_chat_message(user1, "ping @#{group_1.name}", public_chat_channel)
|
|
|
|
expect(chat_message.group_mentions.map(&:target_id)).to contain_exactly(group_1.id)
|
|
|
|
described_class.call(
|
|
guardian: guardian,
|
|
message_id: chat_message.id,
|
|
message: "ping @#{group_2.name}",
|
|
)
|
|
|
|
expect(chat_message.reload.group_mentions.map(&:target_id)).to contain_exactly(group_2.id)
|
|
end
|
|
|
|
it "deletes a mention record when a group mention was removed on message update" do
|
|
chat_message = create_chat_message(user1, "ping @#{group_1.name}", public_chat_channel)
|
|
|
|
described_class.call(
|
|
guardian: guardian,
|
|
message_id: chat_message.id,
|
|
message: "ping nobody anymore!",
|
|
)
|
|
|
|
expect(group_1.chat_mentions.where(chat_message: chat_message).count).to be(0)
|
|
end
|
|
|
|
it "doesn't notify the second time users that has already been notified when creating the message" do
|
|
group_user = Fabricate(:user)
|
|
public_chat_channel.add(group_user)
|
|
group =
|
|
Fabricate(
|
|
:public_group,
|
|
users: [group_user],
|
|
mentionable_level: Group::ALIAS_LEVELS[:everyone],
|
|
)
|
|
|
|
chat_message =
|
|
create_chat_message(user1, "Mentioning @#{group.name}", public_chat_channel)
|
|
expect(group_user.notifications.count).to be(1)
|
|
notification_id = group_user.notifications.first.id
|
|
|
|
described_class.call(
|
|
guardian: guardian,
|
|
message_id: chat_message.id,
|
|
message: "Update the message and still mention the same group @#{group.name}",
|
|
)
|
|
|
|
expect(group_user.notifications.count).to be(1) # no new notifications has been created
|
|
expect(group_user.notifications.first.id).to be(notification_id) # the existing notification hasn't been recreated
|
|
end
|
|
end
|
|
|
|
describe "with @here mentions" do
|
|
it "doesn't notify the second time users that has already been notified when creating the message" do
|
|
user = Fabricate(:user)
|
|
public_chat_channel.add(user)
|
|
user.update!(last_seen_at: 4.minutes.ago)
|
|
|
|
chat_message = create_chat_message(user1, "Mentioning @here", public_chat_channel)
|
|
expect(user.notifications.count).to be(1)
|
|
notification_id = user.notifications.first.id
|
|
|
|
described_class.call(
|
|
guardian: guardian,
|
|
message_id: chat_message.id,
|
|
message: "Update the message and still mention @here",
|
|
)
|
|
|
|
expect(user.notifications.count).to be(1) # no new notifications have been created
|
|
expect(user.notifications.first.id).to be(notification_id) # the existing notification haven't been recreated
|
|
end
|
|
end
|
|
|
|
describe "with @all mentions" do
|
|
it "doesn't notify the second time users that has already been notified when creating the message" do
|
|
user = Fabricate(:user)
|
|
public_chat_channel.add(user)
|
|
|
|
chat_message = create_chat_message(user1, "Mentioning @all", public_chat_channel)
|
|
notification_id = user.notifications.first.id
|
|
|
|
described_class.call(
|
|
guardian: guardian,
|
|
message_id: chat_message.id,
|
|
message: "Update the message and still mention @all",
|
|
)
|
|
|
|
expect(user.notifications.count).to be(1) # no new notifications have been created
|
|
expect(user.notifications.first.id).to be(notification_id) # the existing notification haven't been recreated
|
|
end
|
|
end
|
|
end
|
|
|
|
it "creates a chat_message_revision record and sets last_editor_id for the message" do
|
|
SiteSetting.chat_editing_grace_period = 10
|
|
SiteSetting.chat_editing_grace_period_max_diff_low_trust = 5
|
|
|
|
old_message = "It's a thrsday!"
|
|
new_message = "Today is Thursday, it's almost the weekend already!"
|
|
chat_message = create_chat_message(user1, old_message, public_chat_channel)
|
|
described_class.call(guardian: guardian, message_id: chat_message.id, message: new_message)
|
|
|
|
revision = chat_message.revisions.last
|
|
expect(revision.old_message).to eq(old_message)
|
|
expect(revision.new_message).to eq(new_message)
|
|
expect(revision.user_id).to eq(guardian.user.id)
|
|
expect(chat_message.reload.last_editor_id).to eq(guardian.user.id)
|
|
end
|
|
|
|
describe "duplicates" do
|
|
fab!(:upload1) { Fabricate(:upload, user: user1) }
|
|
fab!(:upload2) { Fabricate(:upload, user: user1) }
|
|
|
|
before do
|
|
SiteSetting.chat_duplicate_message_sensitivity = 1.0
|
|
public_chat_channel.update!(user_count: 50)
|
|
end
|
|
|
|
it "errors when editing the message to be the same as one that was posted recently" do
|
|
chat_message_1 =
|
|
create_chat_message(user1, "this is some chat message", public_chat_channel)
|
|
chat_message_2 =
|
|
create_chat_message(
|
|
Fabricate(:user),
|
|
"another different chat message here",
|
|
public_chat_channel,
|
|
)
|
|
|
|
chat_message_1.update!(created_at: 30.seconds.ago)
|
|
chat_message_2.update!(created_at: 20.seconds.ago)
|
|
|
|
expect do
|
|
described_class.call(
|
|
guardian: guardian,
|
|
message_id: chat_message_1.id,
|
|
message: "another different chat message here",
|
|
)
|
|
end.to raise_error(ActiveRecord::RecordInvalid).with_message(
|
|
"Validation failed: " + I18n.t("chat.errors.duplicate_message"),
|
|
)
|
|
end
|
|
|
|
it "does not count the message as a duplicate when editing leaves the message the same but changes uploads" do
|
|
chat_message =
|
|
create_chat_message(
|
|
user1,
|
|
"this is some chat message",
|
|
public_chat_channel,
|
|
upload_ids: [upload1.id, upload2.id],
|
|
)
|
|
chat_message.update!(created_at: 30.seconds.ago)
|
|
|
|
updater =
|
|
described_class.call(
|
|
guardian: guardian,
|
|
message_id: chat_message.id,
|
|
message: "this is some chat message",
|
|
upload_ids: [upload2.id],
|
|
)
|
|
expect(updater.message).to be_valid
|
|
expect(chat_message.reload.uploads.count).to eq(1)
|
|
end
|
|
end
|
|
|
|
describe "uploads" do
|
|
fab!(:upload1) { Fabricate(:upload, user: user1) }
|
|
fab!(:upload2) { Fabricate(:upload, user: user1) }
|
|
|
|
it "does nothing if the passed in upload_ids match the existing upload_ids" do
|
|
chat_message =
|
|
create_chat_message(
|
|
user1,
|
|
"something",
|
|
public_chat_channel,
|
|
upload_ids: [upload1.id, upload2.id],
|
|
)
|
|
expect {
|
|
described_class.call(
|
|
guardian: guardian,
|
|
message_id: chat_message.id,
|
|
message: "I guess this is different",
|
|
upload_ids: [upload2.id, upload1.id],
|
|
)
|
|
}.to not_change { UploadReference.count }
|
|
end
|
|
|
|
it "removes uploads that should be removed" do
|
|
chat_message =
|
|
create_chat_message(
|
|
user1,
|
|
"something",
|
|
public_chat_channel,
|
|
upload_ids: [upload1.id, upload2.id],
|
|
)
|
|
|
|
expect {
|
|
described_class.call(
|
|
guardian: guardian,
|
|
message_id: chat_message.id,
|
|
message: "I guess this is different",
|
|
upload_ids: [upload1.id],
|
|
)
|
|
}.to change { UploadReference.where(upload_id: upload2.id).count }.by(-1)
|
|
end
|
|
|
|
it "removes all uploads if they should be removed" do
|
|
chat_message =
|
|
create_chat_message(
|
|
user1,
|
|
"something",
|
|
public_chat_channel,
|
|
upload_ids: [upload1.id, upload2.id],
|
|
)
|
|
|
|
expect {
|
|
described_class.call(
|
|
guardian: guardian,
|
|
message_id: chat_message.id,
|
|
message: "I guess this is different",
|
|
upload_ids: [],
|
|
)
|
|
}.to change { UploadReference.where(target: chat_message).count }.by(-2)
|
|
end
|
|
|
|
it "adds one upload if none exist" do
|
|
chat_message = create_chat_message(user1, "something", public_chat_channel)
|
|
expect {
|
|
described_class.call(
|
|
guardian: guardian,
|
|
message_id: chat_message.id,
|
|
message: "I guess this is different",
|
|
upload_ids: [upload1.id],
|
|
)
|
|
}.to change { UploadReference.where(target: chat_message).count }.by(1)
|
|
end
|
|
|
|
it "adds multiple uploads if none exist" do
|
|
chat_message = create_chat_message(user1, "something", public_chat_channel)
|
|
expect {
|
|
described_class.call(
|
|
guardian: guardian,
|
|
message_id: chat_message.id,
|
|
message: "I guess this is different",
|
|
upload_ids: [upload1.id, upload2.id],
|
|
)
|
|
}.to change { UploadReference.where(target: chat_message).count }.by(2)
|
|
end
|
|
|
|
it "doesn't remove existing uploads when upload ids that do not exist are passed in" do
|
|
chat_message =
|
|
create_chat_message(user1, "something", public_chat_channel, upload_ids: [upload1.id])
|
|
expect {
|
|
described_class.call(
|
|
guardian: guardian,
|
|
message_id: chat_message,
|
|
message: "I guess this is different",
|
|
upload_ids: [0],
|
|
)
|
|
}.to not_change { UploadReference.where(target: chat_message).count }
|
|
end
|
|
|
|
it "doesn't add uploads if `chat_allow_uploads` is false" do
|
|
SiteSetting.chat_allow_uploads = false
|
|
chat_message = create_chat_message(user1, "something", public_chat_channel)
|
|
expect {
|
|
described_class.call(
|
|
guardian: guardian,
|
|
message_id: chat_message.id,
|
|
message: "I guess this is different",
|
|
upload_ids: [upload1.id, upload2.id],
|
|
)
|
|
}.to not_change { UploadReference.where(target: chat_message).count }
|
|
end
|
|
|
|
it "doesn't remove existing uploads if `chat_allow_uploads` is false" do
|
|
SiteSetting.chat_allow_uploads = false
|
|
chat_message =
|
|
create_chat_message(
|
|
user1,
|
|
"something",
|
|
public_chat_channel,
|
|
upload_ids: [upload1.id, upload2.id],
|
|
)
|
|
expect {
|
|
described_class.call(
|
|
guardian: guardian,
|
|
message_id: chat_message.id,
|
|
message: "I guess this is different",
|
|
upload_ids: [],
|
|
)
|
|
}.to not_change { UploadReference.where(target: chat_message).count }
|
|
end
|
|
|
|
it "updates if upload is present even if length is less than `chat_minimum_message_length`" do
|
|
chat_message =
|
|
create_chat_message(
|
|
user1,
|
|
"something",
|
|
public_chat_channel,
|
|
upload_ids: [upload1.id, upload2.id],
|
|
)
|
|
SiteSetting.chat_minimum_message_length = 10
|
|
new_message = "hi :)"
|
|
described_class.call(
|
|
guardian: guardian,
|
|
message_id: chat_message.id,
|
|
message: new_message,
|
|
upload_ids: [upload1.id],
|
|
)
|
|
expect(chat_message.reload.message).to eq(new_message)
|
|
end
|
|
end
|
|
|
|
context "when the message is in a thread" do
|
|
fab!(:message) do
|
|
Fabricate(
|
|
:chat_message,
|
|
user: user1,
|
|
chat_channel: public_chat_channel,
|
|
thread: Fabricate(:chat_thread, channel: public_chat_channel),
|
|
)
|
|
end
|
|
|
|
it "publishes a MessageBus event to update the original message metadata" do
|
|
messages =
|
|
MessageBus.track_publish("/chat/#{public_chat_channel.id}") do
|
|
described_class.call(
|
|
guardian: guardian,
|
|
message_id: message.id,
|
|
message: "some new updated content",
|
|
)
|
|
end
|
|
expect(
|
|
messages.find { |m| m.data["type"] == "update_thread_original_message" },
|
|
).to be_present
|
|
end
|
|
end
|
|
|
|
describe "watched words" do
|
|
fab!(:watched_word)
|
|
let!(:censored_word) do
|
|
Fabricate(:watched_word, word: "test", action: WatchedWord.actions[:censor])
|
|
end
|
|
|
|
it "errors when a blocked word is present" do
|
|
chat_message = create_chat_message(user1, "something", public_chat_channel)
|
|
msg = "Validation failed: " + I18n.t("contains_blocked_word", { word: watched_word.word })
|
|
|
|
expect do
|
|
described_class.call(
|
|
guardian: guardian,
|
|
message_id: chat_message.id,
|
|
message: "bad word - #{watched_word.word}",
|
|
)
|
|
end.to raise_error(ActiveRecord::RecordInvalid).with_message(msg)
|
|
|
|
expect(chat_message.reload.message).not_to eq("bad word - #{watched_word.word}")
|
|
end
|
|
|
|
it "hides censored word within the excerpt" do
|
|
chat_message = create_chat_message(user1, "something", public_chat_channel)
|
|
|
|
described_class.call(
|
|
guardian: guardian,
|
|
message_id: chat_message.id,
|
|
message: "bad word - #{censored_word.word}",
|
|
)
|
|
|
|
expect(chat_message.reload.excerpt).to eq("bad word - ■■■■")
|
|
end
|
|
end
|
|
|
|
describe "channel statuses" do
|
|
fab!(:message) { Fabricate(:chat_message, user: user1, chat_channel: public_chat_channel) }
|
|
|
|
def update_message(user)
|
|
message.update!(user: user)
|
|
described_class.call(
|
|
guardian: Guardian.new(user),
|
|
message_id: message.id,
|
|
message: "I guess this is different",
|
|
)
|
|
end
|
|
|
|
context "when channel is closed" do
|
|
before { public_chat_channel.update(status: :closed) }
|
|
|
|
it "errors when trying to update the message for non-staff" do
|
|
updater = update_message(user1)
|
|
expect(message.reload.message).not_to eq("I guess this is different")
|
|
end
|
|
|
|
it "does not error when trying to create a message for staff" do
|
|
update_message(admin1)
|
|
expect(message.reload.message).to eq("I guess this is different")
|
|
end
|
|
end
|
|
|
|
context "when channel is read_only" do
|
|
before { public_chat_channel.update(status: :read_only) }
|
|
|
|
it "errors when trying to update the message for all users" do
|
|
updater = update_message(user1)
|
|
expect(message.reload.message).not_to eq("I guess this is different")
|
|
|
|
updater = update_message(admin1)
|
|
expect(message.reload.message).not_to eq("I guess this is different")
|
|
end
|
|
end
|
|
|
|
context "when channel is archived" do
|
|
before { public_chat_channel.update(status: :archived) }
|
|
|
|
it "errors when trying to update the message for all users" do
|
|
updater = update_message(user1)
|
|
expect(message.reload.message).not_to eq("I guess this is different")
|
|
|
|
updater = update_message(admin1)
|
|
expect(message.reload.message).not_to eq("I guess this is different")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe ".call" do
|
|
subject(:result) { described_class.call(params) }
|
|
|
|
fab!(:current_user) { Fabricate(:user) }
|
|
fab!(:channel_1) { Fabricate(:chat_channel) }
|
|
fab!(:upload_1) { Fabricate(:upload, user: current_user) }
|
|
fab!(:message_1) do
|
|
Fabricate(
|
|
:chat_message,
|
|
chat_channel_id: channel_1.id,
|
|
message: "old",
|
|
upload_ids: [upload_1.id],
|
|
user: current_user,
|
|
)
|
|
end
|
|
|
|
let(:guardian) { current_user.guardian }
|
|
let(:message) { "new" }
|
|
let(:message_id) { message_1.id }
|
|
let(:upload_ids) { [upload_1.id] }
|
|
let(:params) do
|
|
{ guardian: guardian, message_id: message_id, message: message, upload_ids: upload_ids }
|
|
end
|
|
|
|
before do
|
|
SiteSetting.chat_editing_grace_period = 10
|
|
SiteSetting.chat_editing_grace_period_max_diff_low_trust = 10
|
|
SiteSetting.chat_editing_grace_period_max_diff_high_trust = 40
|
|
|
|
channel_1.add(current_user)
|
|
end
|
|
|
|
context "when all steps pass" do
|
|
it "sets the service result as successful" do
|
|
expect(result).to run_service_successfully
|
|
end
|
|
|
|
it "updates the message" do
|
|
expect(result.message.message).to eq("new")
|
|
end
|
|
|
|
it "updates the uploads" do
|
|
upload_1 = Fabricate(:upload, user: current_user)
|
|
upload_2 = Fabricate(:upload, user: current_user)
|
|
params[:upload_ids] = [upload_1.id, upload_2.id]
|
|
|
|
expect(result.message.upload_ids).to contain_exactly(upload_1.id, upload_2.id)
|
|
end
|
|
|
|
it "keeps the existing uploads" do
|
|
expect(result.message.upload_ids).to eq([upload_1.id])
|
|
end
|
|
|
|
it "does not update last editor" do
|
|
# message can only be updated by the original author
|
|
message_1.update!(last_editor: Discourse.system_user)
|
|
|
|
expect { result }.to not_change { result.message.last_editor_id }
|
|
end
|
|
|
|
it "can enqueue a job to process message" do
|
|
params[:process_inline] = false
|
|
expect_enqueued_with(job: Jobs::Chat::ProcessMessage) { result }
|
|
end
|
|
|
|
it "can process a message inline" do
|
|
params[:process_inline] = true
|
|
Jobs::Chat::ProcessMessage.any_instance.expects(:execute).once
|
|
expect_not_enqueued_with(job: Jobs::Chat::ProcessMessage) { result }
|
|
end
|
|
|
|
context "when user is a bot" do
|
|
fab!(:bot) { Discourse.system_user }
|
|
let(:guardian) { Guardian.new(bot) }
|
|
|
|
it "creates the membership" do
|
|
expect { result }.to change { channel_1.membership_for(bot) }.from(nil).to(be_present)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when params are not valid" do
|
|
before { params.delete(:message_id) }
|
|
|
|
it { is_expected.to fail_a_contract }
|
|
end
|
|
|
|
context "when user can't modify a channel message" do
|
|
before { channel_1.update!(status: :read_only) }
|
|
|
|
it { is_expected.to fail_a_policy(:can_modify_channel_message) }
|
|
end
|
|
|
|
context "when user is not member of the channel" do
|
|
let(:message_id) { Fabricate(:chat_message).id }
|
|
|
|
it { is_expected.to fail_to_find_a_model(:membership) }
|
|
end
|
|
|
|
context "when edit grace period" do
|
|
let(:low_trust_char_limit) { SiteSetting.chat_editing_grace_period_max_diff_low_trust }
|
|
let(:high_trust_char_limit) { SiteSetting.chat_editing_grace_period_max_diff_high_trust }
|
|
|
|
it "does not create a revision when under (n) seconds" do
|
|
freeze_time 5.seconds.from_now
|
|
message_1.update!(message: "hello")
|
|
|
|
expect { result }.to not_change { Chat::MessageRevision.count }
|
|
end
|
|
|
|
it "does not create a revision when under (n) chars" do
|
|
message_1.update!(message: "hi :)")
|
|
|
|
expect { result }.to not_change { Chat::MessageRevision.count }
|
|
end
|
|
|
|
it "creates a revision when over (n) seconds" do
|
|
freeze_time 30.seconds.from_now
|
|
message_1.update!(message: "welcome")
|
|
|
|
expect { result }.to change { Chat::MessageRevision.count }.by(1)
|
|
end
|
|
|
|
it "creates a revision when over (n) chars" do
|
|
message_1.update!(message: "a" * (low_trust_char_limit + 1))
|
|
|
|
expect { result }.to change { Chat::MessageRevision.count }.by(1)
|
|
end
|
|
|
|
it "allows trusted users to make larger edits without creating revision" do
|
|
current_user.update!(trust_level: TrustLevel[4])
|
|
message_1.update!(message: "a" * (low_trust_char_limit + 1))
|
|
|
|
expect { result }.to not_change { Chat::MessageRevision.count }
|
|
end
|
|
|
|
it "creates a revision when over (n) chars for high trust users" do
|
|
current_user.update!(trust_level: TrustLevel[4])
|
|
|
|
message_1.update!(message: "a" * (high_trust_char_limit + 1))
|
|
expect { result }.to change { Chat::MessageRevision.count }.by(1)
|
|
end
|
|
end
|
|
end
|
|
end
|