mirror of
https://github.com/discourse/discourse.git
synced 2024-12-17 19:33:46 +08:00
0924f874bd
We've had the UploadReference table for some time now in core, but it was added after ChatUpload was and chat was just never moved over to this new system. This commit changes all chat code dealing with uploads to create/ update/delete/query UploadReference records instead of ChatUpload records for consistency. At a later date we will drop the ChatUpload table, but for now keeping it for data backup. The migration + post migration are the same, we need both in case any chat uploads are added/removed during deploy.
599 lines
20 KiB
Ruby
599 lines
20 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require "rails_helper"
|
|
|
|
describe Chat::ChatMessageUpdater do
|
|
let(:guardian) { Guardian.new(user1) }
|
|
fab!(:admin1) { Fabricate(:admin) }
|
|
fab!(:admin2) { Fabricate(:admin) }
|
|
fab!(:user1) { Fabricate(:user) }
|
|
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 do |user|
|
|
Fabricate(:user_chat_channel_membership, chat_channel: public_chat_channel, user: user)
|
|
end
|
|
Group.refresh_automatic_groups!
|
|
@direct_message_channel =
|
|
Chat::DirectMessageChannelCreator.create!(acting_user: user1, target_users: [user1, user2])
|
|
end
|
|
|
|
def create_chat_message(user, message, channel, upload_ids: nil)
|
|
creator =
|
|
Chat::ChatMessageCreator.create(
|
|
chat_channel: channel,
|
|
user: user,
|
|
in_reply_to_id: nil,
|
|
content: message,
|
|
upload_ids: upload_ids,
|
|
)
|
|
creator.chat_message
|
|
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"
|
|
|
|
updater =
|
|
Chat::ChatMessageUpdater.update(
|
|
guardian: guardian,
|
|
chat_message: chat_message,
|
|
new_content: new_message,
|
|
)
|
|
expect(updater.failed?).to eq(true)
|
|
expect(updater.error.message).to match(
|
|
I18n.t(
|
|
"chat.errors.minimum_length_not_met",
|
|
{ minimum: 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
|
|
|
|
updater =
|
|
Chat::ChatMessageUpdater.update(
|
|
guardian: guardian,
|
|
chat_message: chat_message,
|
|
new_content: new_message,
|
|
)
|
|
expect(updater.failed?).to eq(true)
|
|
expect(updater.error.message).to match(
|
|
I18n.t("chat.errors.message_too_long", { maximum: SiteSetting.chat_maximum_message_length }),
|
|
)
|
|
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 =
|
|
Chat::ChatMessageUpdater.update(
|
|
guardian: Guardian.new(Fabricate(:user)),
|
|
chat_message: chat_message,
|
|
new_content: new_message,
|
|
)
|
|
expect(updater.failed?).to eq(true)
|
|
expect(updater.error).to match(Discourse::InvalidAccess)
|
|
end
|
|
|
|
it "it updates a messages content" do
|
|
chat_message = create_chat_message(user1, "This will be changed", public_chat_channel)
|
|
new_message = "Change to this!"
|
|
|
|
Chat::ChatMessageUpdater.update(
|
|
guardian: guardian,
|
|
chat_message: chat_message,
|
|
new_content: new_message,
|
|
)
|
|
expect(chat_message.reload.message).to eq(new_message)
|
|
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
|
|
Chat::ChatMessageUpdater.update(
|
|
guardian: guardian,
|
|
chat_message: chat_message,
|
|
new_content: "Change to this!",
|
|
)
|
|
end
|
|
expect(events.map { _1[:event_name] }).to include(:chat_message_edited)
|
|
end
|
|
|
|
it "creates mention notifications for unmentioned users" do
|
|
chat_message = create_chat_message(user1, "This will be changed", public_chat_channel)
|
|
expect {
|
|
Chat::ChatMessageUpdater.update(
|
|
guardian: guardian,
|
|
chat_message: chat_message,
|
|
new_content:
|
|
"this is a message with @system @mentions @#{user2.username} and @#{user3.username}",
|
|
)
|
|
}.to change { user2.chat_mentions.count }.by(1).and change { user3.chat_mentions.count }.by(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 {
|
|
Chat::ChatMessageUpdater.update(
|
|
guardian: guardian,
|
|
chat_message: chat_message,
|
|
new_content: message + " editedddd",
|
|
)
|
|
}.not_to change { ChatMention.count }
|
|
end
|
|
|
|
it "doesn't create mentions for users without access" do
|
|
message = "ping"
|
|
chat_message = create_chat_message(user1, message, public_chat_channel)
|
|
|
|
expect {
|
|
Chat::ChatMessageUpdater.update(
|
|
guardian: guardian,
|
|
chat_message: chat_message,
|
|
new_content: message + " @#{user_without_memberships.username}",
|
|
)
|
|
}.not_to change { ChatMention.count }
|
|
end
|
|
|
|
it "destroys mention notifications that should be removed" do
|
|
chat_message =
|
|
create_chat_message(user1, "ping @#{user2.username} @#{user3.username}", public_chat_channel)
|
|
expect {
|
|
Chat::ChatMessageUpdater.update(
|
|
guardian: guardian,
|
|
chat_message: chat_message,
|
|
new_content: "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)
|
|
Chat::ChatMessageUpdater.update(
|
|
guardian: guardian,
|
|
chat_message: chat_message,
|
|
new_content: "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 "does not create new mentions in direct message for users who don't have access" do
|
|
chat_message = create_chat_message(user1, "ping nobody", @direct_message_channel)
|
|
expect {
|
|
Chat::ChatMessageUpdater.update(
|
|
guardian: guardian,
|
|
chat_message: chat_message,
|
|
new_content: "ping @#{admin1.username}",
|
|
)
|
|
}.not_to change { ChatMention.count }
|
|
end
|
|
|
|
describe "group mentions" do
|
|
it "creates group mentions on update" do
|
|
chat_message = create_chat_message(user1, "ping nobody", public_chat_channel)
|
|
expect {
|
|
Chat::ChatMessageUpdater.update(
|
|
guardian: guardian,
|
|
chat_message: chat_message,
|
|
new_content: "ping @#{admin_group.name}",
|
|
)
|
|
}.to change { ChatMention.where(chat_message: chat_message).count }.by(2)
|
|
|
|
expect(admin1.chat_mentions.where(chat_message: chat_message)).to be_present
|
|
expect(admin2.chat_mentions.where(chat_message: chat_message)).to be_present
|
|
end
|
|
|
|
it "doesn't duplicate mentions when the user is already direct mentioned and then group mentioned" do
|
|
chat_message = create_chat_message(user1, "ping @#{admin2.username}", public_chat_channel)
|
|
expect {
|
|
Chat::ChatMessageUpdater.update(
|
|
guardian: guardian,
|
|
chat_message: chat_message,
|
|
new_content: "ping @#{admin_group.name} @#{admin2.username}",
|
|
)
|
|
}.to change { admin1.chat_mentions.count }.by(1).and not_change { admin2.chat_mentions.count }
|
|
end
|
|
|
|
it "deletes old mentions when group mention is removed" do
|
|
chat_message = create_chat_message(user1, "ping @#{admin_group.name}", public_chat_channel)
|
|
expect {
|
|
Chat::ChatMessageUpdater.update(
|
|
guardian: guardian,
|
|
chat_message: chat_message,
|
|
new_content: "ping nobody anymore!",
|
|
)
|
|
}.to change { ChatMention.where(chat_message: chat_message).count }.by(-2)
|
|
|
|
expect(admin1.chat_mentions.where(chat_message: chat_message)).not_to be_present
|
|
expect(admin2.chat_mentions.where(chat_message: chat_message)).not_to be_present
|
|
end
|
|
end
|
|
|
|
it "creates a chat_message_revision record and sets last_editor_id for the message" do
|
|
old_message = "It's a thrsday!"
|
|
new_message = "It's a thursday!"
|
|
chat_message = create_chat_message(user1, old_message, public_chat_channel)
|
|
Chat::ChatMessageUpdater.update(
|
|
guardian: guardian,
|
|
chat_message: chat_message,
|
|
new_content: 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)
|
|
|
|
updater =
|
|
Chat::ChatMessageUpdater.update(
|
|
guardian: guardian,
|
|
chat_message: chat_message_1,
|
|
new_content: "another different chat message here",
|
|
)
|
|
expect(updater.failed?).to eq(true)
|
|
expect(updater.error.message).to eq(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 =
|
|
Chat::ChatMessageUpdater.update(
|
|
guardian: guardian,
|
|
chat_message: chat_message,
|
|
new_content: "this is some chat message",
|
|
upload_ids: [upload2.id],
|
|
)
|
|
expect(updater.failed?).to eq(false)
|
|
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 {
|
|
Chat::ChatMessageUpdater.update(
|
|
guardian: guardian,
|
|
chat_message: chat_message,
|
|
new_content: "I guess this is different",
|
|
upload_ids: [upload2.id, upload1.id],
|
|
)
|
|
}.to not_change { chat_upload_count }.and 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],
|
|
)
|
|
|
|
# TODO (martin) Remove this when we remove ChatUpload completely, 2023-04-01
|
|
DB.exec(<<~SQL)
|
|
INSERT INTO chat_uploads(upload_id, chat_message_id, created_at, updated_at)
|
|
VALUES(#{upload1.id}, #{chat_message.id}, NOW(), NOW())
|
|
SQL
|
|
DB.exec(<<~SQL)
|
|
INSERT INTO chat_uploads(upload_id, chat_message_id, created_at, updated_at)
|
|
VALUES(#{upload2.id}, #{chat_message.id}, NOW(), NOW())
|
|
SQL
|
|
expect {
|
|
Chat::ChatMessageUpdater.update(
|
|
guardian: guardian,
|
|
chat_message: chat_message,
|
|
new_content: "I guess this is different",
|
|
upload_ids: [upload1.id],
|
|
)
|
|
}.to change { chat_upload_count([upload2]) }.by(-1).and 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],
|
|
)
|
|
|
|
# TODO (martin) Remove this when we remove ChatUpload completely, 2023-04-01
|
|
DB.exec(<<~SQL)
|
|
INSERT INTO chat_uploads(upload_id, chat_message_id, created_at, updated_at)
|
|
VALUES(#{upload1.id}, #{chat_message.id}, NOW(), NOW())
|
|
SQL
|
|
DB.exec(<<~SQL)
|
|
INSERT INTO chat_uploads(upload_id, chat_message_id, created_at, updated_at)
|
|
VALUES(#{upload2.id}, #{chat_message.id}, NOW(), NOW())
|
|
SQL
|
|
expect {
|
|
Chat::ChatMessageUpdater.update(
|
|
guardian: guardian,
|
|
chat_message: chat_message,
|
|
new_content: "I guess this is different",
|
|
upload_ids: [],
|
|
)
|
|
}.to change { chat_upload_count([upload1, upload2]) }.by(-2).and 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 {
|
|
Chat::ChatMessageUpdater.update(
|
|
guardian: guardian,
|
|
chat_message: chat_message,
|
|
new_content: "I guess this is different",
|
|
upload_ids: [upload1.id],
|
|
)
|
|
}.to not_change { chat_upload_count([upload1]) }.and 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 {
|
|
Chat::ChatMessageUpdater.update(
|
|
guardian: guardian,
|
|
chat_message: chat_message,
|
|
new_content: "I guess this is different",
|
|
upload_ids: [upload1.id, upload2.id],
|
|
)
|
|
}.to not_change { chat_upload_count([upload1, upload2]) }.and 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 {
|
|
Chat::ChatMessageUpdater.update(
|
|
guardian: guardian,
|
|
chat_message: chat_message,
|
|
new_content: "I guess this is different",
|
|
upload_ids: [0],
|
|
)
|
|
}.to not_change { chat_upload_count }.and 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 {
|
|
Chat::ChatMessageUpdater.update(
|
|
guardian: guardian,
|
|
chat_message: chat_message,
|
|
new_content: "I guess this is different",
|
|
upload_ids: [upload1.id, upload2.id],
|
|
)
|
|
}.to not_change { chat_upload_count([upload1, upload2]) }.and 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 {
|
|
Chat::ChatMessageUpdater.update(
|
|
guardian: guardian,
|
|
chat_message: chat_message,
|
|
new_content: "I guess this is different",
|
|
upload_ids: [],
|
|
)
|
|
}.to not_change { chat_upload_count }.and 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 :)"
|
|
Chat::ChatMessageUpdater.update(
|
|
guardian: guardian,
|
|
chat_message: chat_message,
|
|
new_content: new_message,
|
|
upload_ids: [upload1.id],
|
|
)
|
|
expect(chat_message.reload.message).to eq(new_message)
|
|
end
|
|
end
|
|
|
|
describe "watched words" do
|
|
fab!(:watched_word) { Fabricate(:watched_word) }
|
|
|
|
it "errors when a blocked word is present" do
|
|
chat_message = create_chat_message(user1, "something", public_chat_channel)
|
|
creator =
|
|
Chat::ChatMessageCreator.create(
|
|
chat_channel: public_chat_channel,
|
|
user: user1,
|
|
content: "bad word - #{watched_word.word}",
|
|
)
|
|
expect(creator.failed?).to eq(true)
|
|
expect(creator.error.message).to match(
|
|
I18n.t("contains_blocked_word", { word: watched_word.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)
|
|
Chat::ChatMessageUpdater.update(
|
|
guardian: Guardian.new(user),
|
|
chat_message: message,
|
|
new_content: "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(updater.failed?).to eq(true)
|
|
expect(updater.error.message).to eq(
|
|
I18n.t(
|
|
"chat.errors.channel_modify_message_disallowed",
|
|
status: public_chat_channel.status_name,
|
|
),
|
|
)
|
|
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(updater.failed?).to eq(true)
|
|
expect(updater.error.message).to eq(
|
|
I18n.t(
|
|
"chat.errors.channel_modify_message_disallowed",
|
|
status: public_chat_channel.status_name,
|
|
),
|
|
)
|
|
updater = update_message(admin1)
|
|
expect(updater.failed?).to eq(true)
|
|
expect(updater.error.message).to eq(
|
|
I18n.t(
|
|
"chat.errors.channel_modify_message_disallowed",
|
|
status: public_chat_channel.status_name,
|
|
),
|
|
)
|
|
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(updater.failed?).to eq(true)
|
|
expect(updater.error.message).to eq(
|
|
I18n.t(
|
|
"chat.errors.channel_modify_message_disallowed",
|
|
status: public_chat_channel.status_name,
|
|
),
|
|
)
|
|
updater = update_message(admin1)
|
|
expect(updater.failed?).to eq(true)
|
|
expect(updater.error.message).to eq(
|
|
I18n.t(
|
|
"chat.errors.channel_modify_message_disallowed",
|
|
status: public_chat_channel.status_name,
|
|
),
|
|
)
|
|
end
|
|
end
|
|
end
|
|
|
|
# TODO (martin) Remove this when we remove ChatUpload completely, 2023-04-01
|
|
def chat_upload_count(uploads = nil)
|
|
return DB.query_single("SELECT COUNT(*) FROM chat_uploads").first if !uploads
|
|
DB.query_single(
|
|
"SELECT COUNT(*) FROM chat_uploads WHERE upload_id IN (#{uploads.map(&:id).join(",")})",
|
|
).first
|
|
end
|
|
end
|