mirror of
https://github.com/discourse/discourse.git
synced 2025-03-01 08:49:20 +00:00
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.
620 lines
20 KiB
Ruby
620 lines
20 KiB
Ruby
# frozen_string_literal: true
|
||
|
||
require "rails_helper"
|
||
|
||
describe Chat::ChatMessageCreator do
|
||
fab!(:admin1) { Fabricate(:admin) }
|
||
fab!(:admin2) { Fabricate(:admin) }
|
||
fab!(:user1) { Fabricate(:user, group_ids: [Group::AUTO_GROUPS[:everyone]]) }
|
||
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_group) do
|
||
Fabricate(
|
||
:public_group,
|
||
users: [user1, user2, user3],
|
||
mentionable_level: Group::ALIAS_LEVELS[:everyone],
|
||
)
|
||
end
|
||
fab!(:user_without_memberships) { Fabricate(:user) }
|
||
fab!(:public_chat_channel) { Fabricate(:category_channel) }
|
||
fab!(:dm_chat_channel) do
|
||
Fabricate(
|
||
:direct_message_channel,
|
||
chatable: Fabricate(:direct_message, users: [user1, user2, user3]),
|
||
)
|
||
end
|
||
let(:direct_message_channel) do
|
||
Chat::DirectMessageChannelCreator.create!(acting_user: user1, target_users: [user1, user2])
|
||
end
|
||
|
||
before do
|
||
SiteSetting.chat_enabled = true
|
||
SiteSetting.chat_allowed_groups = Group::AUTO_GROUPS[:everyone]
|
||
SiteSetting.chat_duplicate_message_sensitivity = 0
|
||
|
||
# Create channel memberships
|
||
[admin1, admin2, user1, user2, user3].each do |user|
|
||
Fabricate(:user_chat_channel_membership, chat_channel: public_chat_channel, user: user)
|
||
end
|
||
|
||
Group.refresh_automatic_groups!
|
||
direct_message_channel
|
||
end
|
||
|
||
describe "Integration tests with jobs running immediately" do
|
||
before { Jobs.run_immediately! }
|
||
|
||
it "errors when length is less than `chat_minimum_message_length`" do
|
||
SiteSetting.chat_minimum_message_length = 10
|
||
creator =
|
||
Chat::ChatMessageCreator.create(
|
||
chat_channel: public_chat_channel,
|
||
user: user1,
|
||
content: "2 short",
|
||
)
|
||
expect(creator.failed?).to eq(true)
|
||
expect(creator.error.message).to match(
|
||
I18n.t(
|
||
"chat.errors.minimum_length_not_met",
|
||
{ minimum: SiteSetting.chat_minimum_message_length },
|
||
),
|
||
)
|
||
end
|
||
|
||
it "errors when length is greater than `chat_maximum_message_length`" do
|
||
SiteSetting.chat_maximum_message_length = 100
|
||
creator =
|
||
Chat::ChatMessageCreator.create(
|
||
chat_channel: public_chat_channel,
|
||
user: user1,
|
||
content: "a really long and in depth message that is just too detailed" * 100,
|
||
)
|
||
expect(creator.failed?).to eq(true)
|
||
expect(creator.error.message).to match(
|
||
I18n.t(
|
||
"chat.errors.message_too_long",
|
||
{ maximum: SiteSetting.chat_maximum_message_length },
|
||
),
|
||
)
|
||
end
|
||
|
||
it "allows message creation when length is less than `chat_minimum_message_length` when upload is present" do
|
||
upload = Fabricate(:upload, user: user1)
|
||
SiteSetting.chat_minimum_message_length = 10
|
||
expect {
|
||
Chat::ChatMessageCreator.create(
|
||
chat_channel: public_chat_channel,
|
||
user: user1,
|
||
content: "2 short",
|
||
upload_ids: [upload.id],
|
||
)
|
||
}.to change { ChatMessage.count }.by(1)
|
||
end
|
||
|
||
it "creates messages for users who can see the channel" do
|
||
expect {
|
||
Chat::ChatMessageCreator.create(
|
||
chat_channel: public_chat_channel,
|
||
user: user1,
|
||
content: "this is a message",
|
||
)
|
||
}.to change { ChatMessage.count }.by(1)
|
||
end
|
||
|
||
it "updates the channel’s last message date" do
|
||
previous_last_message_sent_at = public_chat_channel.last_message_sent_at
|
||
|
||
Chat::ChatMessageCreator.create(
|
||
chat_channel: public_chat_channel,
|
||
user: user1,
|
||
content: "this is a message",
|
||
)
|
||
|
||
expect(previous_last_message_sent_at).to be < public_chat_channel.reload.last_message_sent_at
|
||
end
|
||
|
||
it "sets the last_editor_id to the user who created the message" do
|
||
message =
|
||
Chat::ChatMessageCreator.create(
|
||
chat_channel: public_chat_channel,
|
||
user: user1,
|
||
content: "this is a message",
|
||
).chat_message
|
||
expect(message.last_editor_id).to eq(user1.id)
|
||
end
|
||
|
||
it "publishes a DiscourseEvent for new messages" do
|
||
events =
|
||
DiscourseEvent.track_events do
|
||
Chat::ChatMessageCreator.create(
|
||
chat_channel: public_chat_channel,
|
||
user: user1,
|
||
content: "this is a message",
|
||
)
|
||
end
|
||
expect(events.map { _1[:event_name] }).to include(:chat_message_created)
|
||
end
|
||
|
||
it "creates mention notifications for public chat" do
|
||
expect {
|
||
Chat::ChatMessageCreator.create(
|
||
chat_channel: public_chat_channel,
|
||
user: user1,
|
||
content:
|
||
"this is a @#{user1.username} message with @system @mentions @#{user2.username} and @#{user3.username}",
|
||
)
|
||
# Only 2 mentions are created because user mentioned themselves, system, and an invalid username.
|
||
}.to change { ChatMention.count }.by(2).and not_change { user1.chat_mentions.count }
|
||
end
|
||
|
||
it "mentions are case insensitive" do
|
||
expect {
|
||
Chat::ChatMessageCreator.create(
|
||
chat_channel: public_chat_channel,
|
||
user: user1,
|
||
content: "Hey @#{user2.username.upcase}",
|
||
)
|
||
}.to change { user2.chat_mentions.count }.by(1)
|
||
end
|
||
|
||
it "notifies @all properly" do
|
||
expect {
|
||
Chat::ChatMessageCreator.create(
|
||
chat_channel: public_chat_channel,
|
||
user: user1,
|
||
content: "@all",
|
||
)
|
||
}.to change { ChatMention.count }.by(4)
|
||
|
||
UserChatChannelMembership.where(user: user2, chat_channel: public_chat_channel).update_all(
|
||
following: false,
|
||
)
|
||
expect {
|
||
Chat::ChatMessageCreator.create(
|
||
chat_channel: public_chat_channel,
|
||
user: user1,
|
||
content: "again! @all",
|
||
)
|
||
}.to change { ChatMention.count }.by(3)
|
||
end
|
||
|
||
it "notifies @here properly" do
|
||
admin1.update(last_seen_at: 1.year.ago)
|
||
admin2.update(last_seen_at: 1.year.ago)
|
||
user1.update(last_seen_at: Time.now)
|
||
user2.update(last_seen_at: Time.now)
|
||
user3.update(last_seen_at: Time.now)
|
||
expect {
|
||
Chat::ChatMessageCreator.create(
|
||
chat_channel: public_chat_channel,
|
||
user: user1,
|
||
content: "@here",
|
||
)
|
||
}.to change { ChatMention.count }.by(2)
|
||
end
|
||
|
||
it "doesn't sent double notifications when '@here' is mentioned" do
|
||
user2.update(last_seen_at: Time.now)
|
||
expect {
|
||
Chat::ChatMessageCreator.create(
|
||
chat_channel: public_chat_channel,
|
||
user: user1,
|
||
content: "@here @#{user2.username}",
|
||
)
|
||
}.to change { user2.chat_mentions.count }.by(1)
|
||
end
|
||
|
||
it "notifies @here plus other mentions" do
|
||
admin1.update(last_seen_at: Time.now)
|
||
admin2.update(last_seen_at: 1.year.ago)
|
||
user1.update(last_seen_at: 1.year.ago)
|
||
user2.update(last_seen_at: 1.year.ago)
|
||
user3.update(last_seen_at: 1.year.ago)
|
||
expect {
|
||
Chat::ChatMessageCreator.create(
|
||
chat_channel: public_chat_channel,
|
||
user: user1,
|
||
content: "@here plus @#{user3.username}",
|
||
)
|
||
}.to change { user3.chat_mentions.count }.by(1)
|
||
end
|
||
|
||
it "doesn't create mention notifications for users without a membership record" do
|
||
expect {
|
||
Chat::ChatMessageCreator.create(
|
||
chat_channel: public_chat_channel,
|
||
user: user1,
|
||
content: "hello @#{user_without_memberships.username}",
|
||
)
|
||
}.not_to change { ChatMention.count }
|
||
end
|
||
|
||
it "doesn't create mention notifications for users who cannot chat" do
|
||
new_group = Group.create
|
||
SiteSetting.chat_allowed_groups = new_group.id
|
||
expect {
|
||
Chat::ChatMessageCreator.create(
|
||
chat_channel: public_chat_channel,
|
||
user: user1,
|
||
content: "hi @#{user2.username} @#{user3.username}",
|
||
)
|
||
}.not_to change { ChatMention.count }
|
||
end
|
||
|
||
it "doesn't create mention notifications for users with chat disabled" do
|
||
user2.user_option.update(chat_enabled: false)
|
||
expect {
|
||
Chat::ChatMessageCreator.create(
|
||
chat_channel: public_chat_channel,
|
||
user: user1,
|
||
content: "hi @#{user2.username}",
|
||
)
|
||
}.not_to change { ChatMention.count }
|
||
end
|
||
|
||
it "creates only mention notifications for users with access in private chat" do
|
||
expect {
|
||
Chat::ChatMessageCreator.create(
|
||
chat_channel: direct_message_channel,
|
||
user: user1,
|
||
content: "hello there @#{user2.username} and @#{user3.username}",
|
||
)
|
||
# Only user2 should be notified
|
||
}.to change { user2.chat_mentions.count }.by(1).and not_change { user3.chat_mentions.count }
|
||
end
|
||
|
||
it "creates a mention notifications for group users that are participating in private chat" do
|
||
expect {
|
||
Chat::ChatMessageCreator.create(
|
||
chat_channel: direct_message_channel,
|
||
user: user1,
|
||
content: "hello there @#{user_group.name}",
|
||
)
|
||
# Only user2 should be notified
|
||
}.to change { user2.chat_mentions.count }.by(1).and not_change { user3.chat_mentions.count }
|
||
end
|
||
|
||
it "publishes inaccessible mentions when user isn't aren't a part of the channel" do
|
||
ChatPublisher.expects(:publish_inaccessible_mentions).once
|
||
Chat::ChatMessageCreator.create(
|
||
chat_channel: public_chat_channel,
|
||
user: admin1,
|
||
content: "hello @#{user4.username}",
|
||
)
|
||
end
|
||
|
||
it "publishes inaccessible mentions when user doesn't have chat access" do
|
||
SiteSetting.chat_allowed_groups = Group::AUTO_GROUPS[:staff]
|
||
ChatPublisher.expects(:publish_inaccessible_mentions).once
|
||
Chat::ChatMessageCreator.create(
|
||
chat_channel: public_chat_channel,
|
||
user: admin1,
|
||
content: "hello @#{user3.username}",
|
||
)
|
||
end
|
||
|
||
it "doesn't publish inaccessible mentions when user is following channel" do
|
||
ChatPublisher.expects(:publish_inaccessible_mentions).never
|
||
Chat::ChatMessageCreator.create(
|
||
chat_channel: public_chat_channel,
|
||
user: admin1,
|
||
content: "hello @#{admin2.username}",
|
||
)
|
||
end
|
||
|
||
it "does not create mentions for suspended users" do
|
||
user2.update(suspended_till: Time.now + 10.years)
|
||
expect {
|
||
Chat::ChatMessageCreator.create(
|
||
chat_channel: direct_message_channel,
|
||
user: user1,
|
||
content: "hello @#{user2.username}",
|
||
)
|
||
}.not_to change { user2.chat_mentions.count }
|
||
end
|
||
|
||
it "does not create @all mentions for users when ignore_channel_wide_mention is enabled" do
|
||
expect {
|
||
Chat::ChatMessageCreator.create(
|
||
chat_channel: public_chat_channel,
|
||
user: user1,
|
||
content: "@all",
|
||
)
|
||
}.to change { ChatMention.count }.by(4)
|
||
|
||
user2.user_option.update(ignore_channel_wide_mention: true)
|
||
expect {
|
||
Chat::ChatMessageCreator.create(
|
||
chat_channel: public_chat_channel,
|
||
user: user1,
|
||
content: "hi! @all",
|
||
)
|
||
}.to change { ChatMention.count }.by(3)
|
||
end
|
||
|
||
it "does not create @here mentions for users when ignore_channel_wide_mention is enabled" do
|
||
admin1.update(last_seen_at: 1.year.ago)
|
||
admin2.update(last_seen_at: 1.year.ago)
|
||
user1.update(last_seen_at: Time.now)
|
||
user2.update(last_seen_at: Time.now)
|
||
user2.user_option.update(ignore_channel_wide_mention: true)
|
||
user3.update(last_seen_at: Time.now)
|
||
|
||
expect {
|
||
Chat::ChatMessageCreator.create(
|
||
chat_channel: public_chat_channel,
|
||
user: user1,
|
||
content: "@here",
|
||
)
|
||
}.to change { ChatMention.count }.by(1)
|
||
end
|
||
|
||
describe "group mentions" do
|
||
it "creates chat mentions for group mentions where the group is mentionable" do
|
||
expect {
|
||
Chat::ChatMessageCreator.create(
|
||
chat_channel: public_chat_channel,
|
||
user: user1,
|
||
content: "hello @#{admin_group.name}",
|
||
)
|
||
}.to change { admin1.chat_mentions.count }.by(1).and change {
|
||
admin2.chat_mentions.count
|
||
}.by(1)
|
||
end
|
||
|
||
it "doesn't mention users twice if they are direct mentioned and group mentioned" do
|
||
expect {
|
||
Chat::ChatMessageCreator.create(
|
||
chat_channel: public_chat_channel,
|
||
user: user1,
|
||
content: "hello @#{admin_group.name} @#{admin1.username} and @#{admin2.username}",
|
||
)
|
||
}.to change { admin1.chat_mentions.count }.by(1).and change {
|
||
admin2.chat_mentions.count
|
||
}.by(1)
|
||
end
|
||
|
||
it "creates chat mentions for group mentions and direct mentions" do
|
||
expect {
|
||
Chat::ChatMessageCreator.create(
|
||
chat_channel: public_chat_channel,
|
||
user: user1,
|
||
content: "hello @#{admin_group.name} @#{user2.username}",
|
||
)
|
||
}.to change { admin1.chat_mentions.count }.by(1).and change {
|
||
admin2.chat_mentions.count
|
||
}.by(1).and change { user2.chat_mentions.count }.by(1)
|
||
end
|
||
|
||
it "creates chat mentions for group mentions and direct mentions" do
|
||
expect {
|
||
Chat::ChatMessageCreator.create(
|
||
chat_channel: public_chat_channel,
|
||
user: user1,
|
||
content: "hello @#{admin_group.name} @#{user_group.name}",
|
||
)
|
||
}.to change { admin1.chat_mentions.count }.by(1).and change {
|
||
admin2.chat_mentions.count
|
||
}.by(1).and change { user2.chat_mentions.count }.by(1).and change {
|
||
user3.chat_mentions.count
|
||
}.by(1)
|
||
end
|
||
|
||
it "doesn't create chat mentions for group mentions where the group is un-mentionable" do
|
||
admin_group.update(mentionable_level: Group::ALIAS_LEVELS[:nobody])
|
||
expect {
|
||
Chat::ChatMessageCreator.create(
|
||
chat_channel: public_chat_channel,
|
||
user: user1,
|
||
content: "hello @#{admin_group.name}",
|
||
)
|
||
}.not_to change { ChatMention.count }
|
||
end
|
||
end
|
||
|
||
describe "push notifications" do
|
||
before do
|
||
UserChatChannelMembership.where(user: user1, chat_channel: public_chat_channel).update(
|
||
mobile_notification_level: UserChatChannelMembership::NOTIFICATION_LEVELS[:always],
|
||
)
|
||
PresenceChannel.clear_all!
|
||
end
|
||
|
||
it "sends a push notification to watching users who are not in chat" do
|
||
PostAlerter.expects(:push_notification).once
|
||
Chat::ChatMessageCreator.create(
|
||
chat_channel: public_chat_channel,
|
||
user: user2,
|
||
content: "Beep boop",
|
||
)
|
||
end
|
||
|
||
it "does not send a push notification to watching users who are in chat" do
|
||
PresenceChannel.new("/chat/online").present(user_id: user1.id, client_id: 1)
|
||
PostAlerter.expects(:push_notification).never
|
||
Chat::ChatMessageCreator.create(
|
||
chat_channel: public_chat_channel,
|
||
user: user2,
|
||
content: "Beep boop",
|
||
)
|
||
end
|
||
end
|
||
|
||
describe "with uploads" do
|
||
fab!(:upload1) { Fabricate(:upload, user: user1) }
|
||
fab!(:upload2) { Fabricate(:upload, user: user1) }
|
||
fab!(:private_upload) { Fabricate(:upload, user: user2) }
|
||
|
||
it "can attach 1 upload to a new message" do
|
||
expect {
|
||
Chat::ChatMessageCreator.create(
|
||
chat_channel: public_chat_channel,
|
||
user: user1,
|
||
content: "Beep boop",
|
||
upload_ids: [upload1.id],
|
||
)
|
||
}.to not_change { chat_upload_count([upload1]) }.and change {
|
||
UploadReference.where(upload_id: upload1.id).count
|
||
}.by(1)
|
||
end
|
||
|
||
it "can attach multiple uploads to a new message" do
|
||
expect {
|
||
Chat::ChatMessageCreator.create(
|
||
chat_channel: public_chat_channel,
|
||
user: user1,
|
||
content: "Beep boop",
|
||
upload_ids: [upload1.id, upload2.id],
|
||
)
|
||
}.to not_change { chat_upload_count([upload1, upload2]) }.and change {
|
||
UploadReference.where(upload_id: [upload1.id, upload2.id]).count
|
||
}.by(2)
|
||
end
|
||
|
||
it "filters out uploads that weren't uploaded by the user" do
|
||
expect {
|
||
Chat::ChatMessageCreator.create(
|
||
chat_channel: public_chat_channel,
|
||
user: user1,
|
||
content: "Beep boop",
|
||
upload_ids: [private_upload.id],
|
||
)
|
||
}.not_to change { chat_upload_count([private_upload]) }
|
||
end
|
||
|
||
it "doesn't attach uploads when `chat_allow_uploads` is false" do
|
||
SiteSetting.chat_allow_uploads = false
|
||
expect {
|
||
Chat::ChatMessageCreator.create(
|
||
chat_channel: public_chat_channel,
|
||
user: user1,
|
||
content: "Beep boop",
|
||
upload_ids: [upload1.id],
|
||
)
|
||
}.to not_change { chat_upload_count([upload1]) }.and not_change {
|
||
UploadReference.where(upload_id: upload1.id).count
|
||
}
|
||
end
|
||
end
|
||
end
|
||
|
||
it "destroys draft after message was created" do
|
||
ChatDraft.create!(user: user1, chat_channel: public_chat_channel, data: "{}")
|
||
|
||
expect do
|
||
Chat::ChatMessageCreator.create(
|
||
chat_channel: public_chat_channel,
|
||
user: user1,
|
||
content: "Hi @#{user2.username}",
|
||
)
|
||
end.to change { ChatDraft.count }.by(-1)
|
||
end
|
||
|
||
describe "watched words" do
|
||
fab!(:watched_word) { Fabricate(:watched_word) }
|
||
|
||
it "errors when a blocked word is present" do
|
||
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
|
||
def create_message(user)
|
||
Chat::ChatMessageCreator.create(
|
||
chat_channel: public_chat_channel,
|
||
user: user,
|
||
content: "test message",
|
||
)
|
||
end
|
||
|
||
context "when channel is closed" do
|
||
before { public_chat_channel.update(status: :closed) }
|
||
|
||
it "errors when trying to create the message for non-staff" do
|
||
creator = create_message(user1)
|
||
expect(creator.failed?).to eq(true)
|
||
expect(creator.error.message).to eq(
|
||
I18n.t(
|
||
"chat.errors.channel_new_message_disallowed",
|
||
status: public_chat_channel.status_name,
|
||
),
|
||
)
|
||
end
|
||
|
||
it "does not error when trying to create a message for staff" do
|
||
expect { create_message(admin1) }.to change { ChatMessage.count }.by(1)
|
||
end
|
||
end
|
||
|
||
context "when channel is read_only" do
|
||
before { public_chat_channel.update(status: :read_only) }
|
||
|
||
it "errors when trying to create the message for all users" do
|
||
creator = create_message(user1)
|
||
expect(creator.failed?).to eq(true)
|
||
expect(creator.error.message).to eq(
|
||
I18n.t(
|
||
"chat.errors.channel_new_message_disallowed",
|
||
status: public_chat_channel.status_name,
|
||
),
|
||
)
|
||
creator = create_message(admin1)
|
||
expect(creator.failed?).to eq(true)
|
||
expect(creator.error.message).to eq(
|
||
I18n.t(
|
||
"chat.errors.channel_new_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 create the message for all users" do
|
||
creator = create_message(user1)
|
||
expect(creator.failed?).to eq(true)
|
||
expect(creator.error.message).to eq(
|
||
I18n.t(
|
||
"chat.errors.channel_new_message_disallowed",
|
||
status: public_chat_channel.status_name,
|
||
),
|
||
)
|
||
creator = create_message(admin1)
|
||
expect(creator.failed?).to eq(true)
|
||
expect(creator.error.message).to eq(
|
||
I18n.t(
|
||
"chat.errors.channel_new_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)
|
||
DB.query_single(
|
||
"SELECT COUNT(*) FROM chat_uploads WHERE upload_id IN (#{uploads.map(&:id).join(",")})",
|
||
).first
|
||
end
|
||
end
|