After #19666, we send notifications asynchronously. However, this has dramatically delayed the different post-send warnings we send. This change moves all the post-send warnings logic from `Chat::ChatNotifier` into a separate service, allowing us to run this part synchronously, displaying the warnings almost instantly without doing all the heavy lifting for sending notifications. It also simplifies the notifier, making another refactor we planned simpler. Finally, this removes the need to run jobs on the system specs that test the post-warning logic, hopefully making them faster.
366 lines
13 KiB
Ruby
366 lines
13 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require "rails_helper"
|
|
|
|
describe Chat::ChatNotifier do
|
|
describe "#notify_new" do
|
|
fab!(:channel) { Fabricate(:category_channel) }
|
|
fab!(:user_1) { Fabricate(:user) }
|
|
fab!(:user_2) { Fabricate(:user) }
|
|
|
|
before do
|
|
@chat_group =
|
|
Fabricate(
|
|
:group,
|
|
users: [user_1, user_2],
|
|
mentionable_level: Group::ALIAS_LEVELS[:everyone],
|
|
)
|
|
SiteSetting.chat_allowed_groups = @chat_group.id
|
|
|
|
[user_1, user_2].each do |u|
|
|
Fabricate(:user_chat_channel_membership, chat_channel: channel, user: u)
|
|
end
|
|
end
|
|
|
|
def build_cooked_msg(message_body, user, chat_channel: channel)
|
|
ChatMessage.new(
|
|
chat_channel: chat_channel,
|
|
user: user,
|
|
message: message_body,
|
|
created_at: 5.minutes.ago,
|
|
).tap(&:cook)
|
|
end
|
|
|
|
shared_examples "channel-wide mentions" do
|
|
it "returns an empty list when the message doesn't include a channel mention" do
|
|
msg = build_cooked_msg(mention.gsub("@", ""), user_1)
|
|
|
|
to_notify = described_class.new(msg, msg.created_at).notify_new
|
|
|
|
expect(to_notify[list_key]).to be_empty
|
|
end
|
|
|
|
it "will never include someone who is not accepting channel-wide notifications" do
|
|
user_2.user_option.update!(ignore_channel_wide_mention: true)
|
|
msg = build_cooked_msg(mention, user_1)
|
|
|
|
to_notify = described_class.new(msg, msg.created_at).notify_new
|
|
|
|
expect(to_notify[list_key]).to be_empty
|
|
end
|
|
|
|
it "will never mention when channel is not accepting channel wide mentions" do
|
|
channel.update!(allow_channel_wide_mentions: false)
|
|
msg = build_cooked_msg(mention, user_1)
|
|
|
|
to_notify = described_class.new(msg, msg.created_at).notify_new
|
|
|
|
expect(to_notify[list_key]).to be_empty
|
|
end
|
|
|
|
it "includes all members of a channel except the sender" do
|
|
msg = build_cooked_msg(mention, user_1)
|
|
|
|
to_notify = described_class.new(msg, msg.created_at).notify_new
|
|
|
|
expect(to_notify[list_key]).to contain_exactly(user_2.id)
|
|
end
|
|
end
|
|
|
|
shared_examples "ensure only channel members are notified" do
|
|
it "will never include someone outside the channel" do
|
|
user3 = Fabricate(:user)
|
|
@chat_group.add(user3)
|
|
another_channel = Fabricate(:category_channel)
|
|
Fabricate(:user_chat_channel_membership, chat_channel: another_channel, user: user3)
|
|
msg = build_cooked_msg(mention, user_1)
|
|
|
|
to_notify = described_class.new(msg, msg.created_at).notify_new
|
|
|
|
expect(to_notify[list_key]).to contain_exactly(user_2.id)
|
|
end
|
|
|
|
it "will never include someone not following the channel anymore" do
|
|
user3 = Fabricate(:user)
|
|
@chat_group.add(user3)
|
|
Fabricate(
|
|
:user_chat_channel_membership,
|
|
following: false,
|
|
chat_channel: channel,
|
|
user: user3,
|
|
)
|
|
msg = build_cooked_msg(mention, user_1)
|
|
|
|
to_notify = described_class.new(msg, msg.created_at).notify_new
|
|
|
|
expect(to_notify[list_key]).to contain_exactly(user_2.id)
|
|
end
|
|
|
|
it "will never include someone who is suspended" do
|
|
user3 = Fabricate(:user, suspended_till: 2.years.from_now)
|
|
@chat_group.add(user3)
|
|
Fabricate(
|
|
:user_chat_channel_membership,
|
|
following: true,
|
|
chat_channel: channel,
|
|
user: user3,
|
|
)
|
|
|
|
msg = build_cooked_msg(mention, user_1)
|
|
|
|
to_notify = described_class.new(msg, msg.created_at).notify_new
|
|
|
|
expect(to_notify[list_key]).to contain_exactly(user_2.id)
|
|
end
|
|
end
|
|
|
|
describe "global_mentions" do
|
|
let(:mention) { "hello @all!" }
|
|
let(:list_key) { :global_mentions }
|
|
|
|
include_examples "channel-wide mentions"
|
|
include_examples "ensure only channel members are notified"
|
|
|
|
describe "users ignoring or muting the user creating the message" do
|
|
it "does not send notifications to the user who is muting the acting user" do
|
|
Fabricate(:muted_user, user: user_2, muted_user: user_1)
|
|
msg = build_cooked_msg(mention, user_1)
|
|
|
|
to_notify = described_class.new(msg, msg.created_at).notify_new
|
|
|
|
expect(to_notify[list_key]).to be_empty
|
|
end
|
|
|
|
it "does not send notifications to the user who is ignoring the acting user" do
|
|
Fabricate(:ignored_user, user: user_2, ignored_user: user_1, expiring_at: 1.day.from_now)
|
|
msg = build_cooked_msg(mention, user_1)
|
|
|
|
to_notify = described_class.new(msg, msg.created_at).notify_new
|
|
|
|
expect(to_notify[:direct_mentions]).to be_empty
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "here_mentions" do
|
|
let(:mention) { "hello @here!" }
|
|
let(:list_key) { :here_mentions }
|
|
|
|
before { user_2.update!(last_seen_at: 4.minutes.ago) }
|
|
|
|
include_examples "channel-wide mentions"
|
|
include_examples "ensure only channel members are notified"
|
|
|
|
it "includes users seen less than 5 minutes ago" do
|
|
msg = build_cooked_msg(mention, user_1)
|
|
|
|
to_notify = described_class.new(msg, msg.created_at).notify_new
|
|
|
|
expect(to_notify[list_key]).to contain_exactly(user_2.id)
|
|
end
|
|
|
|
it "excludes users seen more than 5 minutes ago" do
|
|
user_2.update!(last_seen_at: 6.minutes.ago)
|
|
msg = build_cooked_msg(mention, user_1)
|
|
|
|
to_notify = described_class.new(msg, msg.created_at).notify_new
|
|
|
|
expect(to_notify[list_key]).to be_empty
|
|
end
|
|
|
|
it "excludes users mentioned directly" do
|
|
msg = build_cooked_msg("hello @here @#{user_2.username}!", user_1)
|
|
|
|
to_notify = described_class.new(msg, msg.created_at).notify_new
|
|
|
|
expect(to_notify[list_key]).to be_empty
|
|
end
|
|
|
|
describe "users ignoring or muting the user creating the message" do
|
|
it "does not send notifications to the user who is muting the acting user" do
|
|
Fabricate(:muted_user, user: user_2, muted_user: user_1)
|
|
msg = build_cooked_msg(mention, user_1)
|
|
|
|
to_notify = described_class.new(msg, msg.created_at).notify_new
|
|
|
|
expect(to_notify[list_key]).to be_empty
|
|
end
|
|
|
|
it "does not send notifications to the user who is ignoring the acting user" do
|
|
Fabricate(:ignored_user, user: user_2, ignored_user: user_1, expiring_at: 1.day.from_now)
|
|
msg = build_cooked_msg(mention, user_1)
|
|
|
|
to_notify = described_class.new(msg, msg.created_at).notify_new
|
|
|
|
expect(to_notify[:direct_mentions]).to be_empty
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "direct_mentions" do
|
|
it "only include mentioned users who are already in the channel" do
|
|
user_3 = Fabricate(:user)
|
|
@chat_group.add(user_3)
|
|
another_channel = Fabricate(:category_channel)
|
|
Fabricate(:user_chat_channel_membership, chat_channel: another_channel, user: user_3)
|
|
msg = build_cooked_msg("Is @#{user_3.username} here? And @#{user_2.username}", user_1)
|
|
|
|
to_notify = described_class.new(msg, msg.created_at).notify_new
|
|
|
|
expect(to_notify[:direct_mentions]).to contain_exactly(user_2.id)
|
|
end
|
|
|
|
it "include users as direct mentions even if there's a @here mention" do
|
|
msg = build_cooked_msg("Hello @here and @#{user_2.username}", user_1)
|
|
|
|
to_notify = described_class.new(msg, msg.created_at).notify_new
|
|
|
|
expect(to_notify[:here_mentions]).to be_empty
|
|
expect(to_notify[:direct_mentions]).to contain_exactly(user_2.id)
|
|
end
|
|
|
|
it "include users as direct mentions even if there's a @all mention" do
|
|
msg = build_cooked_msg("Hello @all and @#{user_2.username}", user_1)
|
|
|
|
to_notify = described_class.new(msg, msg.created_at).notify_new
|
|
|
|
expect(to_notify[:global_mentions]).to be_empty
|
|
expect(to_notify[:direct_mentions]).to contain_exactly(user_2.id)
|
|
end
|
|
|
|
describe "users ignoring or muting the user creating the message" do
|
|
it "does not publish new mentions to these users" do
|
|
Fabricate(:muted_user, user: user_2, muted_user: user_1)
|
|
msg = build_cooked_msg("hey @#{user_2.username} stop muting me!", user_1)
|
|
|
|
ChatPublisher.expects(:publish_new_mention).never
|
|
to_notify = described_class.new(msg, msg.created_at).notify_new
|
|
end
|
|
|
|
it "does not send notifications to the user who is muting the acting user" do
|
|
Fabricate(:muted_user, user: user_2, muted_user: user_1)
|
|
msg = build_cooked_msg("hey @#{user_2.username} stop muting me!", user_1)
|
|
|
|
to_notify = described_class.new(msg, msg.created_at).notify_new
|
|
|
|
expect(to_notify[:direct_mentions]).to be_empty
|
|
end
|
|
|
|
it "does not send notifications to the user who is ignoring the acting user" do
|
|
Fabricate(:ignored_user, user: user_2, ignored_user: user_1, expiring_at: 1.day.from_now)
|
|
msg = build_cooked_msg("hey @#{user_2.username} stop ignoring me!", user_1)
|
|
|
|
to_notify = described_class.new(msg, msg.created_at).notify_new
|
|
|
|
expect(to_notify[:direct_mentions]).to be_empty
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "group mentions" do
|
|
fab!(:user_3) { Fabricate(:user) }
|
|
fab!(:group) do
|
|
Fabricate(
|
|
:public_group,
|
|
users: [user_2, user_3],
|
|
mentionable_level: Group::ALIAS_LEVELS[:everyone],
|
|
)
|
|
end
|
|
fab!(:other_channel) { Fabricate(:category_channel) }
|
|
|
|
before { @chat_group.add(user_3) }
|
|
|
|
let(:mention) { "hello @#{group.name}!" }
|
|
let(:list_key) { group.name }
|
|
|
|
include_examples "ensure only channel members are notified"
|
|
|
|
it "calls guardian can_join_chat_channel?" do
|
|
Guardian.any_instance.expects(:can_join_chat_channel?).at_least_once
|
|
msg = build_cooked_msg("Hello @#{group.name} and @#{user_2.username}", user_1)
|
|
to_notify = described_class.new(msg, msg.created_at).notify_new
|
|
end
|
|
|
|
it "establishes a far-left precedence among group mentions" do
|
|
Fabricate(
|
|
:user_chat_channel_membership,
|
|
chat_channel: channel,
|
|
user: user_3,
|
|
following: true,
|
|
)
|
|
msg = build_cooked_msg("Hello @#{@chat_group.name} and @#{group.name}", user_1)
|
|
|
|
to_notify = described_class.new(msg, msg.created_at).notify_new
|
|
|
|
expect(to_notify[@chat_group.name]).to contain_exactly(user_2.id, user_3.id)
|
|
expect(to_notify[list_key]).to be_empty
|
|
|
|
second_msg = build_cooked_msg("Hello @#{group.name} and @#{@chat_group.name}", user_1)
|
|
|
|
to_notify_2 = described_class.new(second_msg, second_msg.created_at).notify_new
|
|
|
|
expect(to_notify_2[list_key]).to contain_exactly(user_2.id, user_3.id)
|
|
expect(to_notify_2[@chat_group.name]).to be_empty
|
|
end
|
|
|
|
it "skips groups with too many members" do
|
|
SiteSetting.max_users_notified_per_group_mention = (group.user_count - 1)
|
|
|
|
msg = build_cooked_msg("Hello @#{group.name}", user_1)
|
|
|
|
to_notify = described_class.new(msg, msg.created_at).notify_new
|
|
|
|
expect(to_notify[group.name]).to be_nil
|
|
end
|
|
|
|
it "respects the 'max_mentions_per_chat_message' setting and skips notifications" do
|
|
SiteSetting.max_mentions_per_chat_message = 1
|
|
|
|
msg = build_cooked_msg("Hello @#{user_2.username} and @#{user_3.username}", user_1)
|
|
|
|
to_notify = described_class.new(msg, msg.created_at).notify_new
|
|
|
|
expect(to_notify[:direct_mentions]).to be_empty
|
|
expect(to_notify[group.name]).to be_nil
|
|
end
|
|
|
|
it "respects the max mentions setting and skips notifications when mixing users and groups" do
|
|
SiteSetting.max_mentions_per_chat_message = 1
|
|
|
|
msg = build_cooked_msg("Hello @#{user_2.username} and @#{group.name}", user_1)
|
|
|
|
to_notify = described_class.new(msg, msg.created_at).notify_new
|
|
|
|
expect(to_notify[:direct_mentions]).to be_empty
|
|
expect(to_notify[group.name]).to be_nil
|
|
end
|
|
|
|
describe "users ignoring or muting the user creating the message" do
|
|
it "does not send notifications to the user inside the group who is muting the acting user" do
|
|
group.add(user_3)
|
|
Fabricate(:user_chat_channel_membership, chat_channel: channel, user: user_3)
|
|
Fabricate(:muted_user, user: user_2, muted_user: user_1)
|
|
msg = build_cooked_msg("Hello @#{group.name}", user_1)
|
|
|
|
to_notify = described_class.new(msg, msg.created_at).notify_new
|
|
|
|
expect(to_notify[:direct_mentions]).to be_empty
|
|
expect(to_notify[group.name]).to contain_exactly(user_3.id)
|
|
end
|
|
|
|
it "does not send notifications to the uster inside the group who is ignoring the acting user" do
|
|
group.add(user_3)
|
|
Fabricate(:user_chat_channel_membership, chat_channel: channel, user: user_3)
|
|
Fabricate(:ignored_user, user: user_2, ignored_user: user_1, expiring_at: 1.day.from_now)
|
|
msg = build_cooked_msg("Hello @#{group.name}", user_1)
|
|
|
|
to_notify = described_class.new(msg, msg.created_at).notify_new
|
|
|
|
expect(to_notify[:direct_mentions]).to be_empty
|
|
expect(to_notify[group.name]).to contain_exactly(user_3.id)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|