mirror of
https://github.com/discourse/discourse.git
synced 2025-05-22 07:53:49 +08:00
DEV: properly namespace chat (#20690)
This commit main goal was to comply with Zeitwerk and properly rely on autoloading. To achieve this, most resources have been namespaced under the `Chat` module. - Given all models are now namespaced with `Chat::` and would change the stored types in DB when using polymorphism or STI (single table inheritance), this commit uses various Rails methods to ensure proper class is loaded and the stored name in DB is unchanged, eg: `Chat::Message` model will be stored as `"ChatMessage"`, and `"ChatMessage"` will correctly load `Chat::Message` model. - Jobs are now using constants only, eg: `Jobs::Chat::Foo` and should only be enqueued this way Notes: - This commit also used this opportunity to limit the number of registered css files in plugin.rb - `discourse_dev` support has been removed within this commit and will be reintroduced later <!-- NOTE: All pull requests should have tests (rspec in Ruby, qunit in JavaScript). If your code does not include test coverage, please include an explanation of why it was omitted. -->
This commit is contained in:
285
plugins/chat/spec/components/chat/mailer_spec.rb
Normal file
285
plugins/chat/spec/components/chat/mailer_spec.rb
Normal file
@ -0,0 +1,285 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "rails_helper"
|
||||
|
||||
describe Chat::Mailer do
|
||||
fab!(:chatters_group) { Fabricate(:group) }
|
||||
fab!(:sender) { Fabricate(:user, group_ids: [chatters_group.id]) }
|
||||
fab!(:user_1) { Fabricate(:user, group_ids: [chatters_group.id], last_seen_at: 15.minutes.ago) }
|
||||
fab!(:chat_channel) { Fabricate(:category_channel) }
|
||||
fab!(:chat_message) { Fabricate(:chat_message, user: sender, chat_channel: chat_channel) }
|
||||
fab!(:user_1_chat_channel_membership) do
|
||||
Fabricate(
|
||||
:user_chat_channel_membership,
|
||||
user: user_1,
|
||||
chat_channel: chat_channel,
|
||||
last_read_message_id: nil,
|
||||
)
|
||||
end
|
||||
fab!(:private_chat_channel) do
|
||||
Group.refresh_automatic_groups!
|
||||
Chat::DirectMessageChannelCreator.create!(acting_user: sender, target_users: [sender, user_1])
|
||||
end
|
||||
|
||||
before do
|
||||
SiteSetting.chat_enabled = true
|
||||
SiteSetting.chat_allowed_groups = chatters_group.id
|
||||
|
||||
Fabricate(:user_chat_channel_membership, user: sender, chat_channel: chat_channel)
|
||||
end
|
||||
|
||||
def assert_summary_skipped
|
||||
expect(
|
||||
job_enqueued?(job: :user_email, args: { type: "chat_summary", user_id: user_1.id }),
|
||||
).to eq(false)
|
||||
end
|
||||
|
||||
def assert_only_queued_once
|
||||
expect_job_enqueued(job: :user_email, args: { type: "chat_summary", user_id: user_1.id })
|
||||
expect(Jobs::UserEmail.jobs.size).to eq(1)
|
||||
end
|
||||
|
||||
describe "for chat mentions" do
|
||||
fab!(:mention) { Fabricate(:chat_mention, user: user_1, chat_message: chat_message) }
|
||||
|
||||
it "skips users without chat access" do
|
||||
chatters_group.remove(user_1)
|
||||
|
||||
described_class.send_unread_mentions_summary
|
||||
|
||||
assert_summary_skipped
|
||||
end
|
||||
|
||||
it "skips users with summaries disabled" do
|
||||
user_1.user_option.update(chat_email_frequency: UserOption.chat_email_frequencies[:never])
|
||||
|
||||
described_class.send_unread_mentions_summary
|
||||
|
||||
assert_summary_skipped
|
||||
end
|
||||
|
||||
it "skips a job if the user haven't read the channel since the last summary" do
|
||||
user_1_chat_channel_membership.update!(last_unread_mention_when_emailed_id: chat_message.id)
|
||||
|
||||
described_class.send_unread_mentions_summary
|
||||
|
||||
assert_summary_skipped
|
||||
end
|
||||
|
||||
it "skips without chat enabled" do
|
||||
user_1.user_option.update(
|
||||
chat_enabled: false,
|
||||
chat_email_frequency: UserOption.chat_email_frequencies[:when_away],
|
||||
)
|
||||
|
||||
described_class.send_unread_mentions_summary
|
||||
|
||||
assert_summary_skipped
|
||||
end
|
||||
|
||||
it "queues a job for users that was mentioned and never read the channel before" do
|
||||
described_class.send_unread_mentions_summary
|
||||
|
||||
assert_only_queued_once
|
||||
end
|
||||
|
||||
it "skips the job when the user was mentioned but already read the message" do
|
||||
user_1_chat_channel_membership.update!(last_read_message_id: chat_message.id)
|
||||
|
||||
described_class.send_unread_mentions_summary
|
||||
|
||||
assert_summary_skipped
|
||||
end
|
||||
|
||||
it "skips the job when the user is not following a public channel anymore" do
|
||||
user_1_chat_channel_membership.update!(
|
||||
last_read_message_id: chat_message.id - 1,
|
||||
following: false,
|
||||
)
|
||||
|
||||
described_class.send_unread_mentions_summary
|
||||
|
||||
assert_summary_skipped
|
||||
end
|
||||
|
||||
it "doesn’t skip the job when the user is not following a direct channel" do
|
||||
private_chat_channel
|
||||
.user_chat_channel_memberships
|
||||
.where(user_id: user_1.id)
|
||||
.update!(last_read_message_id: chat_message.id - 1, following: false)
|
||||
|
||||
described_class.send_unread_mentions_summary
|
||||
|
||||
assert_only_queued_once
|
||||
end
|
||||
|
||||
it "skips users with unread messages from a different channel" do
|
||||
user_1_chat_channel_membership.update!(last_read_message_id: chat_message.id)
|
||||
second_channel = Fabricate(:category_channel)
|
||||
Fabricate(
|
||||
:user_chat_channel_membership,
|
||||
user: user_1,
|
||||
chat_channel: second_channel,
|
||||
last_read_message_id: chat_message.id - 1,
|
||||
)
|
||||
|
||||
described_class.send_unread_mentions_summary
|
||||
|
||||
assert_summary_skipped
|
||||
end
|
||||
|
||||
it "only queues the job once for users who are member of multiple groups with chat access" do
|
||||
chatters_group_2 = Fabricate(:group, users: [user_1])
|
||||
SiteSetting.chat_allowed_groups = [chatters_group, chatters_group_2].map(&:id).join("|")
|
||||
|
||||
described_class.send_unread_mentions_summary
|
||||
|
||||
assert_only_queued_once
|
||||
end
|
||||
|
||||
it "skips users when the mention was deleted" do
|
||||
chat_message.trash!
|
||||
|
||||
described_class.send_unread_mentions_summary
|
||||
|
||||
assert_summary_skipped
|
||||
end
|
||||
|
||||
it "queues the job if the user has unread mentions and already read all the messages in the previous summary" do
|
||||
user_1_chat_channel_membership.update!(
|
||||
last_read_message_id: chat_message.id,
|
||||
last_unread_mention_when_emailed_id: chat_message.id,
|
||||
)
|
||||
unread_message = Fabricate(:chat_message, chat_channel: chat_channel, user: sender)
|
||||
Fabricate(:chat_mention, user: user_1, chat_message: unread_message)
|
||||
|
||||
described_class.send_unread_mentions_summary
|
||||
|
||||
expect_job_enqueued(job: :user_email, args: { type: "chat_summary", user_id: user_1.id })
|
||||
expect(Jobs::UserEmail.jobs.size).to eq(1)
|
||||
end
|
||||
|
||||
it "skips users who were seen recently" do
|
||||
user_1.update!(last_seen_at: 2.minutes.ago)
|
||||
|
||||
described_class.send_unread_mentions_summary
|
||||
|
||||
assert_summary_skipped
|
||||
end
|
||||
|
||||
it "doesn't mix mentions from other users" do
|
||||
mention.destroy!
|
||||
user_2 = Fabricate(:user, groups: [chatters_group], last_seen_at: 20.minutes.ago)
|
||||
Fabricate(
|
||||
:user_chat_channel_membership,
|
||||
user: user_2,
|
||||
chat_channel: chat_channel,
|
||||
last_read_message_id: nil,
|
||||
)
|
||||
new_message = Fabricate(:chat_message, chat_channel: chat_channel, user: sender)
|
||||
Fabricate(:chat_mention, user: user_2, chat_message: new_message)
|
||||
|
||||
described_class.send_unread_mentions_summary
|
||||
|
||||
expect(
|
||||
job_enqueued?(job: :user_email, args: { type: "chat_summary", user_id: user_1.id }),
|
||||
).to eq(false)
|
||||
expect_job_enqueued(job: :user_email, args: { type: "chat_summary", user_id: user_2.id })
|
||||
expect(Jobs::UserEmail.jobs.size).to eq(1)
|
||||
end
|
||||
|
||||
it "skips users when the message is older than 1 week" do
|
||||
chat_message.update!(created_at: 1.5.weeks.ago)
|
||||
|
||||
described_class.send_unread_mentions_summary
|
||||
|
||||
assert_summary_skipped
|
||||
end
|
||||
|
||||
it "queues a job when the chat_allowed_groups is set to everyone" do
|
||||
SiteSetting.chat_allowed_groups = Group::AUTO_GROUPS[:everyone]
|
||||
|
||||
described_class.send_unread_mentions_summary
|
||||
|
||||
assert_only_queued_once
|
||||
end
|
||||
|
||||
describe "update the user membership after we send the email" do
|
||||
before { Jobs.run_immediately! }
|
||||
|
||||
it "doesn't send the same summary the summary again if the user haven't read any channel messages since the last one" do
|
||||
user_1_chat_channel_membership.update!(last_read_message_id: chat_message.id - 1)
|
||||
described_class.send_unread_mentions_summary
|
||||
|
||||
expect(user_1_chat_channel_membership.reload.last_unread_mention_when_emailed_id).to eq(
|
||||
chat_message.id,
|
||||
)
|
||||
|
||||
another_channel_message = Fabricate(:chat_message, chat_channel: chat_channel, user: sender)
|
||||
Fabricate(:chat_mention, user: user_1, chat_message: another_channel_message)
|
||||
|
||||
expect { described_class.send_unread_mentions_summary }.not_to change(
|
||||
Jobs::UserEmail.jobs,
|
||||
:size,
|
||||
)
|
||||
end
|
||||
|
||||
it "only updates the last_message_read_when_emailed_id on the channel with unread mentions" do
|
||||
another_channel = Fabricate(:category_channel)
|
||||
another_channel_message =
|
||||
Fabricate(:chat_message, chat_channel: another_channel, user: sender)
|
||||
Fabricate(:chat_mention, user: user_1, chat_message: another_channel_message)
|
||||
another_channel_membership =
|
||||
Fabricate(
|
||||
:user_chat_channel_membership,
|
||||
user: user_1,
|
||||
chat_channel: another_channel,
|
||||
last_read_message_id: another_channel_message.id,
|
||||
)
|
||||
user_1_chat_channel_membership.update!(last_read_message_id: chat_message.id - 1)
|
||||
|
||||
described_class.send_unread_mentions_summary
|
||||
|
||||
expect(user_1_chat_channel_membership.reload.last_unread_mention_when_emailed_id).to eq(
|
||||
chat_message.id,
|
||||
)
|
||||
expect(another_channel_membership.reload.last_unread_mention_when_emailed_id).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "for direct messages" do
|
||||
before { Fabricate(:chat_message, user: sender, chat_channel: private_chat_channel) }
|
||||
|
||||
it "queue a job when the user has unread private mentions" do
|
||||
described_class.send_unread_mentions_summary
|
||||
|
||||
assert_only_queued_once
|
||||
end
|
||||
|
||||
it "only queues the job once when the user has mentions and private messages" do
|
||||
Fabricate(:chat_mention, user: user_1, chat_message: chat_message)
|
||||
|
||||
described_class.send_unread_mentions_summary
|
||||
|
||||
assert_only_queued_once
|
||||
end
|
||||
|
||||
it "doesn't mix or update mentions from other users when joining tables" do
|
||||
user_2 = Fabricate(:user, groups: [chatters_group], last_seen_at: 20.minutes.ago)
|
||||
user_2_membership =
|
||||
Fabricate(
|
||||
:user_chat_channel_membership,
|
||||
user: user_2,
|
||||
chat_channel: chat_channel,
|
||||
last_read_message_id: chat_message.id,
|
||||
)
|
||||
Fabricate(:chat_mention, user: user_2, chat_message: chat_message)
|
||||
|
||||
described_class.send_unread_mentions_summary
|
||||
|
||||
assert_only_queued_once
|
||||
expect(user_2_membership.reload.last_unread_mention_when_emailed_id).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
952
plugins/chat/spec/components/chat/message_creator_spec.rb
Normal file
952
plugins/chat/spec/components/chat/message_creator_spec.rb
Normal file
@ -0,0 +1,952 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "rails_helper"
|
||||
|
||||
describe Chat::MessageCreator 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 =
|
||||
described_class.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",
|
||||
{ count: 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 =
|
||||
described_class.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", { count: 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 {
|
||||
described_class.create(
|
||||
chat_channel: public_chat_channel,
|
||||
user: user1,
|
||||
content: "2 short",
|
||||
upload_ids: [upload.id],
|
||||
)
|
||||
}.to change { Chat::Message.count }.by(1)
|
||||
end
|
||||
|
||||
it "creates messages for users who can see the channel" do
|
||||
expect {
|
||||
described_class.create(
|
||||
chat_channel: public_chat_channel,
|
||||
user: user1,
|
||||
content: "this is a message",
|
||||
)
|
||||
}.to change { Chat::Message.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
|
||||
|
||||
described_class.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 =
|
||||
described_class.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
|
||||
described_class.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 mentions and mention notifications for public chat" do
|
||||
message =
|
||||
described_class.create(
|
||||
chat_channel: public_chat_channel,
|
||||
user: user1,
|
||||
content:
|
||||
"this is a @#{user1.username} message with @system @mentions @#{user2.username} and @#{user3.username}",
|
||||
).chat_message
|
||||
|
||||
# a mention for the user himself wasn't created
|
||||
user1_mention = user1.chat_mentions.where(chat_message: message).first
|
||||
expect(user1_mention).to be_nil
|
||||
|
||||
system_user_mention = Discourse.system_user.chat_mentions.where(chat_message: message).first
|
||||
expect(system_user_mention).to be_nil
|
||||
|
||||
user2_mention = user2.chat_mentions.where(chat_message: message).first
|
||||
expect(user2_mention).to be_present
|
||||
expect(user2_mention.notification).to be_present
|
||||
|
||||
user3_mention = user3.chat_mentions.where(chat_message: message).first
|
||||
expect(user3_mention).to be_present
|
||||
expect(user3_mention.notification).to be_present
|
||||
end
|
||||
|
||||
it "mentions are case insensitive" do
|
||||
expect {
|
||||
described_class.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 {
|
||||
described_class.create(chat_channel: public_chat_channel, user: user1, content: "@all")
|
||||
}.to change { Chat::Mention.count }.by(4)
|
||||
|
||||
Chat::UserChatChannelMembership.where(
|
||||
user: user2,
|
||||
chat_channel: public_chat_channel,
|
||||
).update_all(following: false)
|
||||
expect {
|
||||
described_class.create(
|
||||
chat_channel: public_chat_channel,
|
||||
user: user1,
|
||||
content: "again! @all",
|
||||
)
|
||||
}.to change { Chat::Mention.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 {
|
||||
described_class.create(chat_channel: public_chat_channel, user: user1, content: "@here")
|
||||
}.to change { Chat::Mention.count }.by(2)
|
||||
end
|
||||
|
||||
it "doesn't sent double notifications when '@here' is mentioned" do
|
||||
user2.update(last_seen_at: Time.now)
|
||||
expect {
|
||||
described_class.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 {
|
||||
described_class.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
|
||||
message =
|
||||
described_class.create(
|
||||
chat_channel: public_chat_channel,
|
||||
user: user1,
|
||||
content: "hello @#{user_without_memberships.username}",
|
||||
).chat_message
|
||||
|
||||
mention = user_without_memberships.chat_mentions.where(chat_message: message).first
|
||||
expect(mention.notification).to be_nil
|
||||
end
|
||||
|
||||
it "doesn't create mention notifications for users who cannot chat" do
|
||||
new_group = Group.create
|
||||
SiteSetting.chat_allowed_groups = new_group.id
|
||||
|
||||
message =
|
||||
described_class.create(
|
||||
chat_channel: public_chat_channel,
|
||||
user: user1,
|
||||
content: "hi @#{user2.username} @#{user3.username}",
|
||||
).chat_message
|
||||
|
||||
user2_mention = user2.chat_mentions.where(chat_message: message).first
|
||||
expect(user2_mention.notification).to be_nil
|
||||
|
||||
user3_mention = user2.chat_mentions.where(chat_message: message).first
|
||||
expect(user3_mention.notification).to be_nil
|
||||
end
|
||||
|
||||
it "doesn't create mentions for users with chat disabled" do
|
||||
user2.user_option.update(chat_enabled: false)
|
||||
|
||||
message =
|
||||
described_class.create(
|
||||
chat_channel: public_chat_channel,
|
||||
user: user1,
|
||||
content: "hi @#{user2.username}",
|
||||
).chat_message
|
||||
|
||||
mention = user2.chat_mentions.where(chat_message: message).first
|
||||
expect(mention).to be_nil
|
||||
end
|
||||
|
||||
it "creates only mention notifications for users with access in private chat" do
|
||||
message =
|
||||
described_class.create(
|
||||
chat_channel: direct_message_channel,
|
||||
user: user1,
|
||||
content: "hello there @#{user2.username} and @#{user3.username}",
|
||||
).chat_message
|
||||
|
||||
# Only user2 should be notified
|
||||
user2_mention = user2.chat_mentions.where(chat_message: message).first
|
||||
expect(user2_mention.notification).to be_present
|
||||
|
||||
user3_mention = user3.chat_mentions.where(chat_message: message).first
|
||||
expect(user3_mention.notification).to be_nil
|
||||
end
|
||||
|
||||
it "creates a mention for group users even if they're not participating in private chat" do
|
||||
expect {
|
||||
described_class.create(
|
||||
chat_channel: direct_message_channel,
|
||||
user: user1,
|
||||
content: "hello there @#{user_group.name}",
|
||||
)
|
||||
}.to change { user2.chat_mentions.count }.by(1).and change { user3.chat_mentions.count }.by(1)
|
||||
end
|
||||
|
||||
it "creates a mention notifications only for group users that are participating in private chat" do
|
||||
message =
|
||||
described_class.create(
|
||||
chat_channel: direct_message_channel,
|
||||
user: user1,
|
||||
content: "hello there @#{user_group.name}",
|
||||
).chat_message
|
||||
|
||||
user2_mention = user2.chat_mentions.where(chat_message: message).first
|
||||
expect(user2_mention.notification).to be_present
|
||||
|
||||
user3_mention = user3.chat_mentions.where(chat_message: message).first
|
||||
expect(user3_mention.notification).to be_nil
|
||||
end
|
||||
|
||||
it "publishes inaccessible mentions when user isn't aren't a part of the channel" do
|
||||
Chat::Publisher.expects(:publish_inaccessible_mentions).once
|
||||
described_class.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]
|
||||
Chat::Publisher.expects(:publish_inaccessible_mentions).once
|
||||
described_class.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
|
||||
Chat::Publisher.expects(:publish_inaccessible_mentions).never
|
||||
described_class.create(
|
||||
chat_channel: public_chat_channel,
|
||||
user: admin1,
|
||||
content: "hello @#{admin2.username}",
|
||||
)
|
||||
end
|
||||
|
||||
it "creates mentions for suspended users" do
|
||||
user2.update(suspended_till: Time.now + 10.years)
|
||||
|
||||
message =
|
||||
described_class.create(
|
||||
chat_channel: direct_message_channel,
|
||||
user: user1,
|
||||
content: "hello @#{user2.username}",
|
||||
).chat_message
|
||||
|
||||
mention = user2.chat_mentions.where(chat_message: message).first
|
||||
expect(mention).to be_present
|
||||
end
|
||||
|
||||
it "does not create mention notifications for suspended users" do
|
||||
user2.update(suspended_till: Time.now + 10.years)
|
||||
|
||||
message =
|
||||
described_class.create(
|
||||
chat_channel: direct_message_channel,
|
||||
user: user1,
|
||||
content: "hello @#{user2.username}",
|
||||
).chat_message
|
||||
|
||||
mention = user2.chat_mentions.where(chat_message: message).first
|
||||
expect(mention.notification).to be_nil
|
||||
end
|
||||
|
||||
context "when ignore_channel_wide_mention is enabled" do
|
||||
before { user2.user_option.update(ignore_channel_wide_mention: true) }
|
||||
|
||||
it "when mentioning @all creates a mention without notification" do
|
||||
message =
|
||||
described_class.create(
|
||||
chat_channel: public_chat_channel,
|
||||
user: user1,
|
||||
content: "hi! @all",
|
||||
).chat_message
|
||||
|
||||
mention = user2.chat_mentions.where(chat_message: message).first
|
||||
expect(mention).to be_present
|
||||
expect(mention.notification).to be_nil
|
||||
end
|
||||
|
||||
it "when mentioning @here creates a mention without notification" do
|
||||
user2.update(last_seen_at: Time.now)
|
||||
|
||||
message =
|
||||
described_class.create(
|
||||
chat_channel: public_chat_channel,
|
||||
user: user1,
|
||||
content: "@here",
|
||||
).chat_message
|
||||
|
||||
mention = user2.chat_mentions.where(chat_message: message).first
|
||||
expect(mention).to be_present
|
||||
expect(mention.notification).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "replies" do
|
||||
fab!(:reply_message) do
|
||||
Fabricate(:chat_message, chat_channel: public_chat_channel, user: user2)
|
||||
end
|
||||
fab!(:unrelated_message_1) { Fabricate(:chat_message, chat_channel: public_chat_channel) }
|
||||
fab!(:unrelated_message_2) { Fabricate(:chat_message, chat_channel: public_chat_channel) }
|
||||
|
||||
it "links the message that the user is replying to" do
|
||||
message =
|
||||
described_class.create(
|
||||
chat_channel: public_chat_channel,
|
||||
user: user1,
|
||||
content: "this is a message",
|
||||
in_reply_to_id: reply_message.id,
|
||||
).chat_message
|
||||
|
||||
expect(message.in_reply_to_id).to eq(reply_message.id)
|
||||
end
|
||||
|
||||
it "creates a thread and includes the original message and the reply" do
|
||||
message = nil
|
||||
expect {
|
||||
message =
|
||||
described_class.create(
|
||||
chat_channel: public_chat_channel,
|
||||
user: user1,
|
||||
content: "this is a message",
|
||||
in_reply_to_id: reply_message.id,
|
||||
).chat_message
|
||||
}.to change { Chat::Thread.count }.by(1)
|
||||
|
||||
expect(message.reload.thread).not_to eq(nil)
|
||||
expect(message.in_reply_to.thread).to eq(message.thread)
|
||||
expect(message.thread.original_message).to eq(reply_message)
|
||||
expect(message.thread.original_message_user).to eq(reply_message.user)
|
||||
end
|
||||
|
||||
context "when the thread_id is provided" do
|
||||
fab!(:existing_thread) { Fabricate(:chat_thread, channel: public_chat_channel) }
|
||||
|
||||
it "does not create a thread when one is passed in" do
|
||||
message = nil
|
||||
expect {
|
||||
message =
|
||||
described_class.create(
|
||||
chat_channel: public_chat_channel,
|
||||
user: user1,
|
||||
content: "this is a message",
|
||||
thread_id: existing_thread.id,
|
||||
).chat_message
|
||||
}.not_to change { Chat::Thread.count }
|
||||
|
||||
expect(message.reload.thread).to eq(existing_thread)
|
||||
end
|
||||
|
||||
it "errors when the thread ID is for a different channel" do
|
||||
other_channel_thread = Fabricate(:chat_thread, channel: Fabricate(:chat_channel))
|
||||
result =
|
||||
described_class.create(
|
||||
chat_channel: public_chat_channel,
|
||||
user: user1,
|
||||
content: "this is a message",
|
||||
thread_id: other_channel_thread.id,
|
||||
)
|
||||
expect(result.error.message).to eq(I18n.t("chat.errors.thread_invalid_for_channel"))
|
||||
end
|
||||
|
||||
it "errors when the thread does not match the in_reply_to thread" do
|
||||
reply_message.update!(thread: existing_thread)
|
||||
result =
|
||||
described_class.create(
|
||||
chat_channel: public_chat_channel,
|
||||
user: user1,
|
||||
content: "this is a message",
|
||||
in_reply_to_id: reply_message.id,
|
||||
thread_id: Fabricate(:chat_thread, channel: public_chat_channel).id,
|
||||
)
|
||||
expect(result.error.message).to eq(I18n.t("chat.errors.thread_does_not_match_parent"))
|
||||
end
|
||||
|
||||
it "errors when the root message does not have a thread ID" do
|
||||
reply_message.update!(thread: nil)
|
||||
result =
|
||||
described_class.create(
|
||||
chat_channel: public_chat_channel,
|
||||
user: user1,
|
||||
content: "this is a message",
|
||||
in_reply_to_id: reply_message.id,
|
||||
thread_id: existing_thread.id,
|
||||
)
|
||||
expect(result.error.message).to eq(I18n.t("chat.errors.thread_does_not_match_parent"))
|
||||
end
|
||||
end
|
||||
|
||||
context "for missing root messages" do
|
||||
fab!(:original_message) do
|
||||
Fabricate(
|
||||
:chat_message,
|
||||
chat_channel: public_chat_channel,
|
||||
user: user2,
|
||||
created_at: 1.day.ago,
|
||||
)
|
||||
end
|
||||
|
||||
before { reply_message.update!(in_reply_to: original_message) }
|
||||
|
||||
it "raises an error when the root message has been trashed" do
|
||||
original_message.trash!
|
||||
result =
|
||||
described_class.create(
|
||||
chat_channel: public_chat_channel,
|
||||
user: user1,
|
||||
content: "this is a message",
|
||||
in_reply_to_id: reply_message.id,
|
||||
)
|
||||
expect(result.error.message).to eq(I18n.t("chat.errors.original_message_not_found"))
|
||||
end
|
||||
|
||||
it "uses the next message in the chain as the root when the root is deleted" do
|
||||
original_message.destroy!
|
||||
described_class.create(
|
||||
chat_channel: public_chat_channel,
|
||||
user: user1,
|
||||
content: "this is a message",
|
||||
in_reply_to_id: reply_message.id,
|
||||
)
|
||||
expect(reply_message.reload.thread).not_to eq(nil)
|
||||
end
|
||||
end
|
||||
|
||||
context "when there is an existing reply chain" do
|
||||
fab!(:old_message_1) do
|
||||
Fabricate(
|
||||
:chat_message,
|
||||
chat_channel: public_chat_channel,
|
||||
user: user1,
|
||||
created_at: 6.hours.ago,
|
||||
)
|
||||
end
|
||||
fab!(:old_message_2) do
|
||||
Fabricate(
|
||||
:chat_message,
|
||||
chat_channel: public_chat_channel,
|
||||
user: user2,
|
||||
in_reply_to: old_message_1,
|
||||
created_at: 4.hours.ago,
|
||||
)
|
||||
end
|
||||
fab!(:old_message_3) do
|
||||
Fabricate(
|
||||
:chat_message,
|
||||
chat_channel: public_chat_channel,
|
||||
user: user1,
|
||||
in_reply_to: old_message_2,
|
||||
created_at: 1.hour.ago,
|
||||
)
|
||||
end
|
||||
|
||||
before do
|
||||
reply_message.update!(
|
||||
created_at: old_message_3.created_at + 1.hour,
|
||||
in_reply_to: old_message_3,
|
||||
)
|
||||
end
|
||||
|
||||
it "creates a thread and updates all the messages in the chain" do
|
||||
thread_count = Chat::Thread.count
|
||||
message =
|
||||
described_class.create(
|
||||
chat_channel: public_chat_channel,
|
||||
user: user1,
|
||||
content: "this is a message",
|
||||
in_reply_to_id: reply_message.id,
|
||||
).chat_message
|
||||
|
||||
expect(Chat::Thread.count).to eq(thread_count + 1)
|
||||
expect(message.reload.thread).not_to eq(nil)
|
||||
expect(message.reload.in_reply_to.thread).to eq(message.thread)
|
||||
expect(old_message_1.reload.thread).to eq(message.thread)
|
||||
expect(old_message_2.reload.thread).to eq(message.thread)
|
||||
expect(old_message_3.reload.thread).to eq(message.thread)
|
||||
expect(message.thread.chat_messages.count).to eq(5)
|
||||
message =
|
||||
described_class.create(
|
||||
chat_channel: public_chat_channel,
|
||||
user: user1,
|
||||
content: "this is a message",
|
||||
in_reply_to_id: reply_message.id,
|
||||
).chat_message
|
||||
end
|
||||
|
||||
context "when a thread already exists and the thread_id is passed in" do
|
||||
let!(:last_message) do
|
||||
described_class.create(
|
||||
chat_channel: public_chat_channel,
|
||||
user: user1,
|
||||
content: "this is a message",
|
||||
in_reply_to_id: reply_message.id,
|
||||
).chat_message
|
||||
end
|
||||
let!(:existing_thread) { last_message.reload.thread }
|
||||
|
||||
it "does not create a new thread" do
|
||||
thread_count = Chat::Thread.count
|
||||
|
||||
message =
|
||||
described_class.create(
|
||||
chat_channel: public_chat_channel,
|
||||
user: user1,
|
||||
content: "this is a message again",
|
||||
in_reply_to_id: last_message.id,
|
||||
thread_id: existing_thread.id,
|
||||
).chat_message
|
||||
|
||||
expect(Chat::Thread.count).to eq(thread_count)
|
||||
expect(message.reload.thread).to eq(existing_thread)
|
||||
expect(message.reload.in_reply_to.thread).to eq(existing_thread)
|
||||
expect(message.thread.chat_messages.count).to eq(6)
|
||||
end
|
||||
|
||||
it "errors when the thread does not match the root thread" do
|
||||
old_message_1.update!(thread: Fabricate(:chat_thread, channel: public_chat_channel))
|
||||
result =
|
||||
described_class.create(
|
||||
chat_channel: public_chat_channel,
|
||||
user: user1,
|
||||
content: "this is a message",
|
||||
in_reply_to_id: reply_message.id,
|
||||
thread_id: existing_thread.id,
|
||||
)
|
||||
expect(result.error.message).to eq(I18n.t("chat.errors.thread_does_not_match_parent"))
|
||||
end
|
||||
|
||||
it "errors when the root message does not have a thread ID" do
|
||||
old_message_1.update!(thread: nil)
|
||||
result =
|
||||
described_class.create(
|
||||
chat_channel: public_chat_channel,
|
||||
user: user1,
|
||||
content: "this is a message",
|
||||
in_reply_to_id: reply_message.id,
|
||||
thread_id: existing_thread.id,
|
||||
)
|
||||
expect(result.error.message).to eq(I18n.t("chat.errors.thread_does_not_match_parent"))
|
||||
end
|
||||
end
|
||||
|
||||
context "when there are hundreds of messages in a reply chain already" do
|
||||
before do
|
||||
previous_message = nil
|
||||
1000.times do |i|
|
||||
previous_message =
|
||||
Fabricate(
|
||||
:chat_message,
|
||||
chat_channel: public_chat_channel,
|
||||
user: [user1, user2].sample,
|
||||
in_reply_to: previous_message,
|
||||
created_at: i.hours.ago,
|
||||
)
|
||||
end
|
||||
@last_message_in_chain = previous_message
|
||||
end
|
||||
|
||||
xit "works" do
|
||||
thread_count = Chat::Thread.count
|
||||
|
||||
message = nil
|
||||
puts Benchmark.measure {
|
||||
message =
|
||||
described_class.create(
|
||||
chat_channel: public_chat_channel,
|
||||
user: user1,
|
||||
content: "this is a message",
|
||||
in_reply_to_id: @last_message_in_chain.id,
|
||||
).chat_message
|
||||
}
|
||||
|
||||
expect(Chat::Thread.count).to eq(thread_count + 1)
|
||||
expect(message.reload.thread).not_to eq(nil)
|
||||
expect(message.reload.in_reply_to.thread).to eq(message.thread)
|
||||
expect(message.thread.chat_messages.count).to eq(1001)
|
||||
end
|
||||
end
|
||||
|
||||
context "if the root message alread had a thread" do
|
||||
fab!(:old_thread) { Fabricate(:chat_thread, original_message: old_message_1) }
|
||||
fab!(:incorrect_thread) { Fabricate(:chat_thread, channel: public_chat_channel) }
|
||||
|
||||
before do
|
||||
old_message_1.update!(thread: old_thread)
|
||||
old_message_3.update!(thread: incorrect_thread)
|
||||
end
|
||||
|
||||
it "does not change any messages in the chain, assumes they have the correct thread ID" do
|
||||
thread_count = Chat::Thread.count
|
||||
message =
|
||||
described_class.create(
|
||||
chat_channel: public_chat_channel,
|
||||
user: user1,
|
||||
content: "this is a message",
|
||||
in_reply_to_id: reply_message.id,
|
||||
).chat_message
|
||||
|
||||
expect(Chat::Thread.count).to eq(thread_count)
|
||||
expect(message.reload.thread).to eq(old_thread)
|
||||
expect(message.reload.in_reply_to.thread).to eq(old_thread)
|
||||
expect(old_message_1.reload.thread).to eq(old_thread)
|
||||
expect(old_message_2.reload.thread).to eq(old_thread)
|
||||
expect(old_message_3.reload.thread).to eq(incorrect_thread)
|
||||
expect(message.thread.chat_messages.count).to eq(4)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "group mentions" do
|
||||
it "creates chat mentions for group mentions where the group is mentionable" do
|
||||
expect {
|
||||
described_class.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 {
|
||||
described_class.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 {
|
||||
described_class.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 {
|
||||
described_class.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 {
|
||||
described_class.create(
|
||||
chat_channel: public_chat_channel,
|
||||
user: user1,
|
||||
content: "hello @#{admin_group.name}",
|
||||
)
|
||||
}.not_to change { Chat::Mention.count }
|
||||
end
|
||||
end
|
||||
|
||||
describe "push notifications" do
|
||||
before do
|
||||
Chat::UserChatChannelMembership.where(
|
||||
user: user1,
|
||||
chat_channel: public_chat_channel,
|
||||
).update(
|
||||
mobile_notification_level: Chat::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
|
||||
described_class.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
|
||||
described_class.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 {
|
||||
described_class.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 {
|
||||
described_class.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 {
|
||||
described_class.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 {
|
||||
described_class.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
|
||||
Chat::Draft.create!(user: user1, chat_channel: public_chat_channel, data: "{}")
|
||||
|
||||
expect do
|
||||
described_class.create(
|
||||
chat_channel: public_chat_channel,
|
||||
user: user1,
|
||||
content: "Hi @#{user2.username}",
|
||||
)
|
||||
end.to change { Chat::Draft.count }.by(-1)
|
||||
end
|
||||
|
||||
describe "watched words" do
|
||||
fab!(:watched_word) { Fabricate(:watched_word) }
|
||||
|
||||
it "errors when a blocked word is present" do
|
||||
creator =
|
||||
described_class.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)
|
||||
described_class.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.closed"),
|
||||
)
|
||||
end
|
||||
|
||||
it "does not error when trying to create a message for staff" do
|
||||
expect { create_message(admin1) }.to change { Chat::Message.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.read_only"),
|
||||
)
|
||||
creator = create_message(admin1)
|
||||
expect(creator.failed?).to eq(true)
|
||||
expect(creator.error.message).to eq(
|
||||
I18n.t("chat.errors.channel_new_message_disallowed.read_only"),
|
||||
)
|
||||
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.archived"),
|
||||
)
|
||||
creator = create_message(admin1)
|
||||
expect(creator.failed?).to eq(true)
|
||||
expect(creator.error.message).to eq(
|
||||
I18n.t("chat.errors.channel_new_message_disallowed.archived"),
|
||||
)
|
||||
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
|
@ -0,0 +1,74 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "rails_helper"
|
||||
|
||||
describe Chat::MessageRateLimiter do
|
||||
fab!(:user) { Fabricate(:user, trust_level: 3) }
|
||||
let(:limiter) { described_class.new(user) }
|
||||
|
||||
before do
|
||||
freeze_time
|
||||
RateLimiter.enable
|
||||
SiteSetting.chat_allowed_messages_for_trust_level_0 = 1
|
||||
SiteSetting.chat_allowed_messages_for_other_trust_levels = 2
|
||||
SiteSetting.chat_auto_silence_duration = 30
|
||||
end
|
||||
|
||||
after { limiter.clear! }
|
||||
|
||||
it "does nothing when rate limits are not exceeded" do
|
||||
limiter.run!
|
||||
expect(user.reload.silenced?).to be false
|
||||
end
|
||||
|
||||
it "silences the user for the correct amount of time when they exceed the limit" do
|
||||
2.times do
|
||||
limiter.run!
|
||||
expect(user.reload.silenced?).to be false
|
||||
end
|
||||
|
||||
expect { limiter.run! }.to raise_error(RateLimiter::LimitExceeded)
|
||||
|
||||
expect(user.reload.silenced?).to be true
|
||||
expect(user.silenced_till).to be_within(0.1).of(30.minutes.from_now)
|
||||
end
|
||||
|
||||
it "silences the user correctly based on trust level" do
|
||||
user.update(trust_level: 0) # Should only be able to run once without hitting limit
|
||||
limiter.run!
|
||||
expect(user.reload.silenced?).to be false
|
||||
expect { limiter.run! }.to raise_error(RateLimiter::LimitExceeded)
|
||||
expect(user.reload.silenced?).to be true
|
||||
end
|
||||
|
||||
it "doesn't hit limit if site setting for allowed messages equals 0" do
|
||||
SiteSetting.chat_allowed_messages_for_other_trust_levels = 0
|
||||
5.times do
|
||||
limiter.run!
|
||||
expect(user.reload.silenced?).to be false
|
||||
end
|
||||
end
|
||||
|
||||
it "doesn't silence the user even when the limit is broken if auto_silence_duration is set to 0" do
|
||||
SiteSetting.chat_allowed_messages_for_other_trust_levels = 1
|
||||
SiteSetting.chat_auto_silence_duration = 0
|
||||
limiter.run!
|
||||
expect(user.reload.silenced?).to be false
|
||||
|
||||
expect { limiter.run! }.to raise_error(RateLimiter::LimitExceeded)
|
||||
expect(user.reload.silenced?).to be false
|
||||
end
|
||||
|
||||
it "logs a staff action when the user is silenced" do
|
||||
SiteSetting.chat_allowed_messages_for_other_trust_levels = 1
|
||||
limiter.run!
|
||||
|
||||
expect { limiter.run! }.to raise_error(RateLimiter::LimitExceeded).and change {
|
||||
UserHistory.where(
|
||||
target_user: user,
|
||||
acting_user: Discourse.system_user,
|
||||
action: UserHistory.actions[:silence_user],
|
||||
).count
|
||||
}.by(1)
|
||||
end
|
||||
end
|
586
plugins/chat/spec/components/chat/message_updater_spec.rb
Normal file
586
plugins/chat/spec/components/chat/message_updater_spec.rb
Normal file
@ -0,0 +1,586 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "rails_helper"
|
||||
|
||||
describe Chat::MessageUpdater 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::MessageCreator.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::MessageUpdater.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",
|
||||
{ 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
|
||||
|
||||
updater =
|
||||
Chat::MessageUpdater.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", { count: 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::MessageUpdater.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::MessageUpdater.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::MessageUpdater.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::MessageUpdater.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::MessageUpdater.update(
|
||||
guardian: guardian,
|
||||
chat_message: chat_message,
|
||||
new_content: 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)
|
||||
|
||||
Chat::MessageUpdater.update(
|
||||
guardian: guardian,
|
||||
chat_message: chat_message,
|
||||
new_content: message + " @#{user_without_memberships.username}",
|
||||
)
|
||||
|
||||
mention = user_without_memberships.chat_mentions.where(chat_message: chat_message).first
|
||||
expect(mention.notification).to be_nil
|
||||
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::MessageUpdater.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::MessageUpdater.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 "doesn't create mention notification in direct message for users without access" do
|
||||
message = create_chat_message(user1, "ping nobody", @direct_message_channel)
|
||||
|
||||
Chat::MessageUpdater.update(
|
||||
guardian: guardian,
|
||||
chat_message: message,
|
||||
new_content: "ping @#{admin1.username}",
|
||||
)
|
||||
|
||||
mention = admin1.chat_mentions.where(chat_message: message).first
|
||||
expect(mention.notification).to be_nil
|
||||
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::MessageUpdater.update(
|
||||
guardian: guardian,
|
||||
chat_message: chat_message,
|
||||
new_content: "ping @#{admin_group.name}",
|
||||
)
|
||||
}.to change { Chat::Mention.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::MessageUpdater.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::MessageUpdater.update(
|
||||
guardian: guardian,
|
||||
chat_message: chat_message,
|
||||
new_content: "ping nobody anymore!",
|
||||
)
|
||||
}.to change { Chat::Mention.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::MessageUpdater.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::MessageUpdater.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::MessageUpdater.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::MessageUpdater.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::MessageUpdater.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::MessageUpdater.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::MessageUpdater.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::MessageUpdater.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::MessageUpdater.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::MessageUpdater.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::MessageUpdater.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::MessageUpdater.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::MessageCreator.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::MessageUpdater.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.closed"),
|
||||
)
|
||||
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.read_only"),
|
||||
)
|
||||
updater = update_message(admin1)
|
||||
expect(updater.failed?).to eq(true)
|
||||
expect(updater.error.message).to eq(
|
||||
I18n.t("chat.errors.channel_modify_message_disallowed.read_only"),
|
||||
)
|
||||
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.archived"),
|
||||
)
|
||||
updater = update_message(admin1)
|
||||
expect(updater.failed?).to eq(true)
|
||||
expect(updater.error.message).to eq(
|
||||
I18n.t("chat.errors.channel_modify_message_disallowed.archived"),
|
||||
)
|
||||
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
|
72
plugins/chat/spec/components/chat/seeder_spec.rb
Normal file
72
plugins/chat/spec/components/chat/seeder_spec.rb
Normal file
@ -0,0 +1,72 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "rails_helper"
|
||||
|
||||
describe Chat::Seeder do
|
||||
fab!(:staff_category) { Fabricate(:private_category, name: "Staff", group: Group[:staff]) }
|
||||
fab!(:general_category) { Fabricate(:category, name: "General") }
|
||||
|
||||
fab!(:staff_user1) do
|
||||
Fabricate(:user, last_seen_at: 1.minute.ago, groups: [Group[:staff], Group[:everyone]])
|
||||
end
|
||||
fab!(:staff_user2) do
|
||||
Fabricate(:user, last_seen_at: 1.minute.ago, groups: [Group[:staff], Group[:everyone]])
|
||||
end
|
||||
|
||||
fab!(:regular_user) { Fabricate(:user, last_seen_at: 1.minute.ago, groups: [Group[:everyone]]) }
|
||||
|
||||
before do
|
||||
SiteSetting.staff_category_id = staff_category.id
|
||||
SiteSetting.general_category_id = general_category.id
|
||||
Jobs.run_immediately!
|
||||
end
|
||||
|
||||
def assert_channel_was_correctly_seeded(channel, group)
|
||||
expect(channel).to be_present
|
||||
expect(channel.auto_join_users).to eq(true)
|
||||
|
||||
expected_members_count = GroupUser.where(group: group).count
|
||||
memberships_count =
|
||||
Chat::UserChatChannelMembership.automatic.where(chat_channel: channel, following: true).count
|
||||
|
||||
expect(memberships_count).to eq(expected_members_count)
|
||||
end
|
||||
|
||||
it "seeds default channels" do
|
||||
Chat::Seeder.new.execute
|
||||
|
||||
staff_channel = Chat::Channel.find_by(chatable_id: staff_category)
|
||||
general_channel = Chat::Channel.find_by(chatable_id: general_category)
|
||||
|
||||
assert_channel_was_correctly_seeded(staff_channel, Group[:staff])
|
||||
assert_channel_was_correctly_seeded(general_channel, Group[:everyone])
|
||||
|
||||
expect(staff_category.custom_fields[Chat::HAS_CHAT_ENABLED]).to eq(true)
|
||||
expect(general_category.reload.custom_fields[Chat::HAS_CHAT_ENABLED]).to eq(true)
|
||||
expect(SiteSetting.needs_chat_seeded).to eq(false)
|
||||
end
|
||||
|
||||
it "applies a name to the general category channel" do
|
||||
expected_name = general_category.name
|
||||
|
||||
Chat::Seeder.new.execute
|
||||
|
||||
general_channel = Chat::Channel.find_by(chatable_id: general_category)
|
||||
expect(general_channel.name).to eq(expected_name)
|
||||
end
|
||||
|
||||
it "applies a name to the staff category channel" do
|
||||
expected_name = staff_category.name
|
||||
|
||||
Chat::Seeder.new.execute
|
||||
|
||||
staff_channel = Chat::Channel.find_by(chatable_id: staff_category)
|
||||
expect(staff_channel.name).to eq(expected_name)
|
||||
end
|
||||
|
||||
it "does nothing when 'SiteSetting.needs_chat_seeded' is false" do
|
||||
SiteSetting.needs_chat_seeded = false
|
||||
|
||||
expect { Chat::Seeder.new.execute }.not_to change { Chat::Channel.count }
|
||||
end
|
||||
end
|
Reference in New Issue
Block a user