discourse-chat-integration/spec/services/manager_spec.rb

459 lines
16 KiB
Ruby

# frozen_string_literal: true
require "rails_helper"
require_dependency "post_creator"
require_relative "../dummy_provider"
RSpec.describe DiscourseChatIntegration::Manager do
let(:manager) { ::DiscourseChatIntegration::Manager }
let(:category) { Fabricate(:category) }
let(:group) { Fabricate(:group) }
let(:group2) { Fabricate(:group) }
let(:topic) { Fabricate(:topic, category_id: category.id) }
let(:first_post) { Fabricate(:post, topic: topic) }
let(:second_post) { Fabricate(:post, topic: topic, post_number: 2) }
describe ".trigger_notifications" do
include_context "with dummy provider"
let(:chan1) { DiscourseChatIntegration::Channel.create!(provider: "dummy") }
let(:chan2) { DiscourseChatIntegration::Channel.create!(provider: "dummy") }
let(:chan3) { DiscourseChatIntegration::Channel.create!(provider: "dummy") }
before { SiteSetting.chat_integration_enabled = true }
it "should fail gracefully when a provider throws an exception" do
DiscourseChatIntegration::Rule.create!(
channel: chan1,
filter: "watch",
category_id: category.id,
)
# Triggering a ProviderError should set the error_key to the error message
provider.set_raise_exception(
DiscourseChatIntegration::ProviderError.new info: { error_key: "hello" }
)
manager.trigger_notifications(first_post.id)
expect(provider.sent_to_channel_ids).to contain_exactly
expect(DiscourseChatIntegration::Channel.all.first.error_key).to eq("hello")
# Triggering a different error should set the error_key to a generic message
provider.set_raise_exception(StandardError.new "hello")
manager.trigger_notifications(first_post.id)
expect(provider.sent_to_channel_ids).to contain_exactly
expect(DiscourseChatIntegration::Channel.all.first.error_key).to eq(
"chat_integration.channel_exception",
)
provider.set_raise_exception(nil)
manager.trigger_notifications(first_post.id)
expect(DiscourseChatIntegration::Channel.all.first.error_key.nil?).to be true
end
it "should not send notifications when provider is disabled" do
SiteSetting.chat_integration_enabled = false
DiscourseChatIntegration::Rule.create!(
channel: chan1,
filter: "watch",
category_id: category.id,
)
manager.trigger_notifications(first_post.id)
expect(provider.sent_to_channel_ids).to contain_exactly
end
it "should send a notification to watched and following channels for new topic" do
DiscourseChatIntegration::Rule.create!(
channel: chan1,
filter: "watch",
category_id: category.id,
)
DiscourseChatIntegration::Rule.create!(
channel: chan2,
filter: "follow",
category_id: category.id,
)
DiscourseChatIntegration::Rule.create!(
channel: chan3,
filter: "mute",
category_id: category.id,
)
manager.trigger_notifications(first_post.id)
expect(provider.sent_to_channel_ids).to contain_exactly(chan1.id, chan2.id)
end
it "should send a notification only to watched for reply" do
DiscourseChatIntegration::Rule.create!(
channel: chan1,
filter: "watch",
category_id: category.id,
)
DiscourseChatIntegration::Rule.create!(
channel: chan2,
filter: "follow",
category_id: category.id,
)
DiscourseChatIntegration::Rule.create!(
channel: chan3,
filter: "mute",
category_id: category.id,
)
manager.trigger_notifications(second_post.id)
expect(provider.sent_to_channel_ids).to contain_exactly(chan1.id)
end
it "should respect wildcard category settings" do
DiscourseChatIntegration::Rule.create!(channel: chan1, filter: "watch", category_id: nil)
manager.trigger_notifications(first_post.id)
expect(provider.sent_to_channel_ids).to contain_exactly(chan1.id)
end
it "should respect mute over watch" do
DiscourseChatIntegration::Rule.create!(channel: chan1, filter: "watch", category_id: nil) # Wildcard watch
DiscourseChatIntegration::Rule.create!(
channel: chan1,
filter: "mute",
category_id: category.id,
) # Specific mute
manager.trigger_notifications(first_post.id)
expect(provider.sent_to_channel_ids).to contain_exactly
end
it "should respect watch over follow" do
DiscourseChatIntegration::Rule.create!(channel: chan1, filter: "follow", category_id: nil) # Wildcard follow
DiscourseChatIntegration::Rule.create!(
channel: chan1,
filter: "watch",
category_id: category.id,
) # Specific watch
manager.trigger_notifications(second_post.id)
expect(provider.sent_to_channel_ids).to contain_exactly(chan1.id)
end
it "should respect thread over watch" do
DiscourseChatIntegration::Rule.create!(channel: chan1, filter: "watch", category_id: nil) # Wildcard watch
DiscourseChatIntegration::Rule.create!(
channel: chan1,
filter: "thread",
category_id: category.id,
) # Specific thread
manager.trigger_notifications(second_post.id)
expect(provider.sent_to_channel_ids).to contain_exactly(chan1.id)
end
it "should not notify about private messages" do
DiscourseChatIntegration::Rule.create!(channel: chan1, filter: "follow", category_id: nil) # Wildcard watch
private_post = Fabricate(:private_message_post)
manager.trigger_notifications(private_post.id)
expect(provider.sent_to_channel_ids).to contain_exactly
end
it "should work for group pms" do
DiscourseChatIntegration::Rule.create!(channel: chan1, filter: "watch") # Wildcard watch
DiscourseChatIntegration::Rule.create!(
channel: chan2,
type: "group_message",
filter: "watch",
group_id: group.id,
) # Group watch
private_post = Fabricate(:private_message_post)
private_post.topic.invite_group(Fabricate(:user), group)
manager.trigger_notifications(private_post.id)
expect(provider.sent_to_channel_ids).to contain_exactly(chan2.id)
end
it "should work for pms with multiple groups" do
DiscourseChatIntegration::Rule.create!(
channel: chan1,
type: "group_message",
filter: "watch",
group_id: group.id,
)
DiscourseChatIntegration::Rule.create!(
channel: chan2,
type: "group_message",
filter: "watch",
group_id: group2.id,
)
private_post = Fabricate(:private_message_post)
private_post.topic.invite_group(Fabricate(:user), group)
private_post.topic.invite_group(Fabricate(:user), group2)
manager.trigger_notifications(private_post.id)
expect(provider.sent_to_channel_ids).to contain_exactly(chan1.id, chan2.id)
end
it "should work for group mentions" do
third_post =
Fabricate(:post, topic: topic, post_number: 3, raw: "let's mention @#{group.name}")
DiscourseChatIntegration::Rule.create!(channel: chan1, filter: "watch") # Wildcard watch
DiscourseChatIntegration::Rule.create!(
channel: chan2,
type: "group_message",
filter: "watch",
group_id: group.id,
)
DiscourseChatIntegration::Rule.create!(
channel: chan3,
type: "group_mention",
filter: "watch",
group_id: group.id,
)
manager.trigger_notifications(third_post.id)
expect(provider.sent_to_channel_ids).to contain_exactly(chan1.id, chan3.id)
end
it "should give group rule precedence over normal rules" do
third_post =
Fabricate(:post, topic: topic, post_number: 3, raw: "let's mention @#{group.name}")
DiscourseChatIntegration::Rule.create!(
channel: chan1,
filter: "mute",
category_id: category.id,
) # Mute category
manager.trigger_notifications(third_post.id)
expect(provider.sent_to_channel_ids).to contain_exactly
DiscourseChatIntegration::Rule.create!(
channel: chan1,
filter: "watch",
type: "group_mention",
group_id: group.id,
) # Watch mentions
manager.trigger_notifications(third_post.id)
expect(provider.sent_to_channel_ids).to contain_exactly(chan1.id)
end
it "should not notify about mentions in private messages" do
# Group 1 watching for messages on channel 1
DiscourseChatIntegration::Rule.create!(
channel: chan1,
filter: "watch",
type: "group_message",
group_id: group.id,
)
# Group 2 watching for mentions on channel 2
DiscourseChatIntegration::Rule.create!(
channel: chan2,
filter: "watch",
type: "group_mention",
group_id: group2.id,
)
# Make a private message only accessible to group 1
private_message = Fabricate(:private_message_post)
private_message.topic.invite_group(Fabricate(:user), group)
# Mention group 2 in the message
mention_post =
Fabricate(
:post,
topic: private_message.topic,
post_number: 2,
raw: "let's mention @#{group2.name}",
)
# We expect that only group 1 receives a notification
manager.trigger_notifications(mention_post.id)
expect(provider.sent_to_channel_ids).to contain_exactly(chan1.id)
end
it "should not notify about posts the chat_user cannot see" do
DiscourseChatIntegration::Rule.create!(channel: chan1, filter: "follow", category_id: nil) # Wildcard watch
# Create a group & user
group = Fabricate(:group, name: "friends")
user = Fabricate(:user, username: "david")
group.add(user)
# Set the chat_user to the newly created non-admin user
SiteSetting.chat_integration_discourse_username = "david"
# Create a category
category = Fabricate(:category, name: "Test category")
topic.category = category
topic.save!
# Restrict category to admins only
category.set_permissions(Group[:admins] => :full)
category.save!
# Check no notification sent
manager.trigger_notifications(first_post.id)
expect(provider.sent_to_channel_ids).to contain_exactly
# Now expose category to new user
category.set_permissions(Group[:friends] => :full)
category.save!
# Check notification sent
manager.trigger_notifications(first_post.id)
expect(provider.sent_to_channel_ids).to contain_exactly(chan1.id)
end
describe "With `create_post_for_category_and_tag_changes` enabled" do
before(:each) { SiteSetting.create_post_for_category_and_tag_changes = true }
let(:admin) { Fabricate(:admin) }
let(:other_topic) { Fabricate(:topic) }
let(:other_topic_post) { Fabricate(:post, topic: topic) }
it "should trigger follow rules for specific categories when topic category changes" do
DiscourseChatIntegration::Rule.create!(
channel: chan1,
filter: "follow",
category_id: category.id,
)
PostRevisor.new(other_topic_post).revise!(admin, category_id: category.id)
manager.trigger_notifications(topic.ordered_posts.last.id)
expect(provider.sent_to_channel_ids).to contain_exactly(chan1.id)
end
it "shouldn't trigger follow rules with wildcard category match" do
DiscourseChatIntegration::Rule.create!(channel: chan1, filter: "follow", category_id: nil)
PostRevisor.new(other_topic_post).revise!(admin, category_id: category.id)
manager.trigger_notifications(topic.ordered_posts.last.id)
expect(provider.sent_to_channel_ids).to contain_exactly
end
end
describe "with tags enabled" do
let(:tag) { Fabricate(:tag, name: "gsoc") }
let(:tagged_topic) { Fabricate(:topic, category_id: category.id, tags: [tag]) }
let(:tagged_first_post) { Fabricate(:post, topic: tagged_topic) }
before(:each) { SiteSetting.tagging_enabled = true }
it "should still work for rules without any tags specified" do
DiscourseChatIntegration::Rule.create!(channel: chan1, filter: "follow", category_id: nil) # Wildcard watch
manager.trigger_notifications(first_post.id)
manager.trigger_notifications(tagged_first_post.id)
expect(provider.sent_to_channel_ids).to contain_exactly(chan1.id, chan1.id)
end
it "should only match tagged topics when rule has tags" do
DiscourseChatIntegration::Rule.create!(
channel: chan1,
filter: "follow",
category_id: category.id,
tags: [tag.name],
)
manager.trigger_notifications(first_post.id)
manager.trigger_notifications(tagged_first_post.id)
expect(provider.sent_to_channel_ids).to contain_exactly(chan1.id)
end
describe "with create_small_action_post_for_tag_changes enabled" do
fab!(:admin) { Fabricate(:admin, refresh_auto_groups: true) }
fab!(:additional_tag) { Fabricate(:tag) }
before { SiteSetting.create_post_for_category_and_tag_changes = true }
def set_new_tags_and_return_small_action_post(tags)
PostRevisor.new(tagged_first_post).revise!(admin, tags: tags)
tagged_topic.ordered_posts.last
end
it "should notify when rule is set up for tag additions for a category with no tag filter" do
post = set_new_tags_and_return_small_action_post([tag.name, additional_tag.name])
DiscourseChatIntegration::Rule.create!(
channel: chan1,
filter: "tag_added",
category_id: category.id,
)
manager.trigger_notifications(post.id)
expect(provider.sent_to_channel_ids).to contain_exactly(chan1.id)
end
it "notifies when topic has a tag added that matches the rule" do
post = set_new_tags_and_return_small_action_post([tag.name, additional_tag.name])
DiscourseChatIntegration::Rule.create!(
channel: chan1,
filter: "tag_added",
category_id: category.id,
tags: [additional_tag.name],
)
manager.trigger_notifications(post.id)
expect(provider.sent_to_channel_ids).to contain_exactly(chan1.id)
end
it "doesn't notify when a new regular post is created" do
DiscourseChatIntegration::Rule.create!(
channel: chan1,
filter: "tag_added",
category_id: nil,
tags: [tag.name],
)
post = Fabricate(:post, topic: tagged_topic)
manager.trigger_notifications(post.id)
expect(provider.sent_to_channel_ids).to contain_exactly
end
it "doesn't notify when topic has an unchanged tag present in the rule, even if a new tag is added" do
post = set_new_tags_and_return_small_action_post([tag.name, additional_tag.name])
DiscourseChatIntegration::Rule.create!(
channel: chan1,
filter: "tag_added",
category_id: category.id,
tags: [tag.name],
)
manager.trigger_notifications(post.id)
expect(provider.sent_to_channel_ids).to contain_exactly
end
it "doesn't notify for small action 'tags_changed' posts unless a matching rule exists" do
post = set_new_tags_and_return_small_action_post([additional_tag.name])
DiscourseChatIntegration::Rule.create!(channel: chan1, filter: "watch", category_id: nil) # Wildcard watch
manager.trigger_notifications(post.id)
expect(provider.sent_to_channel_ids).to contain_exactly
end
end
end
end
end