From 49956bf829bb43b71b698dd95d7bc2c139f7bfb3 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Thu, 29 Dec 2022 12:31:05 +0000 Subject: [PATCH] DEV: Introduce syntax_tree for ruby formatting (#149) --- .github/workflows/plugin-linting.yml | 25 +- .github/workflows/plugin-tests.yml | 6 +- .rubocop.yml | 2 +- .streerc | 2 + Gemfile | 7 +- Gemfile.lock | 4 + app/controllers/chat_controller.rb | 67 ++- app/helpers/helper.rb | 57 ++- app/jobs/onceoff/add_type_field.rb | 4 +- .../onceoff/migrate_from_slack_official.rb | 107 ++-- app/models/channel.rb | 15 +- app/models/plugin_model.rb | 14 +- app/models/rule.rb | 71 +-- app/routes/discourse.rb | 11 +- app/routes/discourse_chat_integration.rb | 24 +- app/serializers/channel_serializer.rb | 2 +- app/services/manager.rb | 50 +- ...030_add_unique_index_to_slack_thread_ts.rb | 9 +- lib/discourse_chat_integration/provider.rb | 33 +- .../provider/discord/discord_provider.rb | 66 ++- .../provider/flowdock/flowdock_provider.rb | 27 +- .../provider/gitter/gitter_provider.rb | 26 +- .../provider/google/google_provider.rb | 93 ++-- .../provider/groupme/groupme_provider.rb | 57 ++- .../provider/guilded/guilded_provider.rb | 66 ++- .../provider/matrix/matrix_provider.rb | 69 ++- .../mattermost_command_controller.rb | 33 +- .../mattermost/mattermost_provider.rb | 52 +- .../rocketchat/rocketchat_provider.rb | 53 +- .../slack/slack_command_controller.rb | 150 +++--- .../slack/slack_enabled_setting_validator.rb | 10 +- .../provider/slack/slack_message.rb | 93 ++-- .../provider/slack/slack_message_formatter.rb | 13 +- .../provider/slack/slack_provider.rb | 102 ++-- .../provider/slack/slack_transcript.rb | 190 +++---- .../provider/teams/teams_provider.rb | 62 ++- .../telegram/telegram_command_controller.rb | 76 +-- .../provider/telegram/telegram_initializer.rb | 2 +- .../provider/telegram/telegram_provider.rb | 80 +-- .../provider/webex/webex_provider.rb | 58 ++- .../provider/zulip/zulip_provider.rb | 57 ++- plugin.rb | 26 +- spec/dummy_provider.rb | 17 +- spec/helpers/helper_spec.rb | 462 ++++++++++------- .../migrate_from_slack_official_spec.rb | 125 ++--- spec/jobs/regular/notify_chats_spec.rb | 42 +- .../provider/discord/discord_provider_spec.rb | 48 +- .../flowdock/flowdock_provider_spec.rb | 31 +- .../provider/gitter/gitter_provider_spec.rb | 34 +- .../provider/google/google_provider_spec.rb | 32 +- .../groupme/groupme_provider_spect.rb | 29 +- .../provider/guilded/guilded_provider_spec.rb | 34 +- .../provider/matrix/matrix_provider_spec.rb | 38 +- .../mattermost_command_controller_spec.rb | 112 +++-- .../mattermost/mattermost_provider_spec.rb | 39 +- .../rocketchat/rocketchat_provider_spec.rb | 27 +- .../slack/slack_command_controller_spec.rb | 474 ++++++++++-------- .../slack/slack_message_formatter_spec.rb | 28 +- .../provider/slack/slack_provider_spec.rb | 138 +++-- .../provider/slack/slack_transcript_spec.rb | 470 +++++++++-------- .../provider/teams/teams_provider_spec.rb | 41 +- .../telegram_command_controller_spec.rb | 180 ++++--- .../telegram/telegram_provider_spec.rb | 42 +- .../provider/webex/webex_provider_spec.rb | 33 +- .../provider/zulip/zulip_provider_spec.rb | 32 +- spec/models/channel_spec.rb | 78 +-- spec/models/rule_spec.rb | 143 +++--- spec/requests/chat_controller_spec.rb | 365 +++++++------- spec/requests/public_controller_spec.rb | 20 +- spec/services/manager_spec.rb | 229 ++++++--- 70 files changed, 3078 insertions(+), 2236 deletions(-) create mode 100644 .streerc diff --git a/.github/workflows/plugin-linting.yml b/.github/workflows/plugin-linting.yml index 6161090..6d2bb97 100644 --- a/.github/workflows/plugin-linting.yml +++ b/.github/workflows/plugin-linting.yml @@ -20,7 +20,7 @@ jobs: - name: Set up Node.js uses: actions/setup-node@v3 with: - node-version: 16 + node-version: 18 cache: yarn - name: Yarn install @@ -33,15 +33,15 @@ jobs: bundler-cache: true - name: ESLint - if: ${{ always() }} - run: yarn eslint --ext .js,.js.es6 --no-error-on-unmatched-pattern {test,assets}/javascripts + if: ${{ !cancelled() }} + run: yarn eslint --ext .js,.js.es6 --no-error-on-unmatched-pattern {test,assets,admin/assets}/javascripts - name: Prettier - if: ${{ always() }} + if: ${{ !cancelled() }} shell: bash run: | yarn prettier -v - if [ 0 -lt $(find assets -type f \( -name "*.scss" -or -name "*.js" -or -name "*.es6" \) 2> /dev/null | wc -l) ]; then + if [ 0 -lt $(find assets admin/assets -type f \( -name "*.scss" -or -name "*.js" -or -name "*.es6" \) 2> /dev/null | wc -l) ]; then yarn prettier --list-different "assets/**/*.{scss,js,es6}" fi if [ 0 -lt $(find test -type f \( -name "*.js" -or -name "*.es6" \) 2> /dev/null | wc -l) ]; then @@ -49,9 +49,18 @@ jobs: fi - name: Ember template lint - if: ${{ always() }} - run: yarn ember-template-lint --no-error-on-unmatched-pattern assets/javascripts + if: ${{ !cancelled() }} + run: yarn ember-template-lint --no-error-on-unmatched-pattern assets/javascripts admin/assets/javascripts - name: Rubocop - if: ${{ always() }} + if: ${{ !cancelled() }} run: bundle exec rubocop . + + - name: Syntax Tree + if: ${{ !cancelled() }} + run: | + if test -f .streerc; then + bundle exec stree check Gemfile $(git ls-files '*.rb') $(git ls-files '*.rake') + else + echo "Stree config not detected for this repository. Skipping." + fi diff --git a/.github/workflows/plugin-tests.yml b/.github/workflows/plugin-tests.yml index 9d390bc..f30a5be 100644 --- a/.github/workflows/plugin-tests.yml +++ b/.github/workflows/plugin-tests.yml @@ -80,7 +80,7 @@ jobs: - name: Get yarn cache directory id: yarn-cache-dir - run: echo "::set-output name=dir::$(yarn cache dir)" + run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT - name: Yarn cache uses: actions/cache@v3 @@ -130,7 +130,7 @@ jobs: shell: bash run: | if [ 0 -lt $(find plugins/${{ github.event.repository.name }}/spec -type f -name "*.rb" 2> /dev/null | wc -l) ]; then - echo "::set-output name=files_exist::true" + echo "files_exist=true" >> $GITHUB_OUTPUT fi - name: Plugin RSpec @@ -142,7 +142,7 @@ jobs: shell: bash run: | if [ 0 -lt $(find plugins/${{ github.event.repository.name }}/test/javascripts -type f \( -name "*.js" -or -name "*.es6" \) 2> /dev/null | wc -l) ]; then - echo "::set-output name=files_exist::true" + echo "files_exist=true" >> $GITHUB_OUTPUT fi - name: Plugin QUnit diff --git a/.rubocop.yml b/.rubocop.yml index d46296c..fb14dfa 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,2 +1,2 @@ inherit_gem: - rubocop-discourse: default.yml + rubocop-discourse: stree-compat.yml diff --git a/.streerc b/.streerc new file mode 100644 index 0000000..0bc4379 --- /dev/null +++ b/.streerc @@ -0,0 +1,2 @@ +--print-width=100 +--plugins=plugin/trailing_comma diff --git a/Gemfile b/Gemfile index 85c1173..8bb1dfc 100644 --- a/Gemfile +++ b/Gemfile @@ -1,8 +1,9 @@ # frozen_string_literal: true -source 'https://rubygems.org' +source "https://rubygems.org" group :development do - gem 'translations-manager', git: 'https://github.com/discourse/translations-manager.git' - gem 'rubocop-discourse' + gem "translations-manager", git: "https://github.com/discourse/translations-manager.git" + gem "rubocop-discourse" + gem "syntax_tree" end diff --git a/Gemfile.lock b/Gemfile.lock index 8766725..efe3eb6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -12,6 +12,7 @@ GEM parallel (1.22.1) parser (3.1.2.1) ast (~> 2.4.1) + prettier_print (1.2.0) rainbow (3.1.1) regexp_parser (2.6.0) rexml (3.2.5) @@ -33,6 +34,8 @@ GEM rubocop-rspec (2.13.2) rubocop (~> 1.33) ruby-progressbar (1.11.0) + syntax_tree (5.1.0) + prettier_print (>= 1.2.0) unicode-display_width (2.3.0) PLATFORMS @@ -40,6 +43,7 @@ PLATFORMS DEPENDENCIES rubocop-discourse + syntax_tree translations-manager! BUNDLED WITH diff --git a/app/controllers/chat_controller.rb b/app/controllers/chat_controller.rb index fa1201d..9d09cae 100644 --- a/app/controllers/chat_controller.rb +++ b/app/controllers/chat_controller.rb @@ -8,15 +8,16 @@ class DiscourseChatIntegration::ChatController < ApplicationController end def list_providers - providers = ::DiscourseChatIntegration::Provider.enabled_providers.map do |x| - { - name: x::PROVIDER_NAME, - id: x::PROVIDER_NAME, - channel_parameters: (defined? x::CHANNEL_PARAMETERS) ? x::CHANNEL_PARAMETERS : [] - } - end + providers = + ::DiscourseChatIntegration::Provider.enabled_providers.map do |x| + { + name: x::PROVIDER_NAME, + id: x::PROVIDER_NAME, + channel_parameters: (defined?(x::CHANNEL_PARAMETERS)) ? x::CHANNEL_PARAMETERS : [], + } + end - render json: providers, root: 'providers' + render json: providers, root: "providers" end def test @@ -28,9 +29,7 @@ class DiscourseChatIntegration::ChatController < ApplicationController provider = ::DiscourseChatIntegration::Provider.get_by_name(channel.provider) - if !DiscourseChatIntegration::Provider.is_enabled(provider) - raise Discourse::NotFound - end + raise Discourse::NotFound if !DiscourseChatIntegration::Provider.is_enabled(provider) post = Topic.find(topic_id.to_i).posts.first @@ -56,34 +55,36 @@ class DiscourseChatIntegration::ChatController < ApplicationController raise Discourse::InvalidParameters if !providers.include?(requested_provider) channels = DiscourseChatIntegration::Channel.with_provider(requested_provider) - render_serialized channels, DiscourseChatIntegration::ChannelSerializer, root: 'channels' + render_serialized channels, DiscourseChatIntegration::ChannelSerializer, root: "channels" end def create_channel begin - providers = ::DiscourseChatIntegration::Provider.enabled_providers.map { |x| x::PROVIDER_NAME } + providers = + ::DiscourseChatIntegration::Provider.enabled_providers.map { |x| x::PROVIDER_NAME } if !defined?(params[:channel]) && defined?(params[:channel][:provider]) - raise Discourse::InvalidParameters, 'Provider is not valid' + raise Discourse::InvalidParameters, "Provider is not valid" end requested_provider = params[:channel][:provider] if !providers.include?(requested_provider) - raise Discourse::InvalidParameters, 'Provider is not valid' + raise Discourse::InvalidParameters, "Provider is not valid" end - allowed_keys = DiscourseChatIntegration::Provider.get_by_name(requested_provider)::CHANNEL_PARAMETERS.map { |p| p[:key].to_sym } + allowed_keys = + DiscourseChatIntegration::Provider.get_by_name( + requested_provider, + )::CHANNEL_PARAMETERS.map { |p| p[:key].to_sym } hash = params.require(:channel).permit(:provider, data: allowed_keys) channel = DiscourseChatIntegration::Channel.new(hash) - if !channel.save - raise Discourse::InvalidParameters, 'Channel is not valid' - end + raise Discourse::InvalidParameters, "Channel is not valid" if !channel.save - render_serialized channel, DiscourseChatIntegration::ChannelSerializer, root: 'channel' + render_serialized channel, DiscourseChatIntegration::ChannelSerializer, root: "channel" rescue Discourse::InvalidParameters => e render json: { errors: [e.message] }, status: 422 end @@ -94,15 +95,16 @@ class DiscourseChatIntegration::ChatController < ApplicationController channel = DiscourseChatIntegration::Channel.find(params[:id].to_i) channel.error_key = nil # Reset any error on the rule - allowed_keys = DiscourseChatIntegration::Provider.get_by_name(channel.provider)::CHANNEL_PARAMETERS.map { |p| p[:key].to_sym } + allowed_keys = + DiscourseChatIntegration::Provider.get_by_name( + channel.provider, + )::CHANNEL_PARAMETERS.map { |p| p[:key].to_sym } hash = params.require(:channel).permit(data: allowed_keys) - if !channel.update(hash) - raise Discourse::InvalidParameters, 'Channel is not valid' - end + raise Discourse::InvalidParameters, "Channel is not valid" if !channel.update(hash) - render_serialized channel, DiscourseChatIntegration::ChannelSerializer, root: 'channel' + render_serialized channel, DiscourseChatIntegration::ChannelSerializer, root: "channel" rescue Discourse::InvalidParameters => e render json: { errors: [e.message] }, status: 422 end @@ -118,14 +120,13 @@ class DiscourseChatIntegration::ChatController < ApplicationController def create_rule begin - hash = params.require(:rule).permit(:channel_id, :type, :filter, :group_id, :category_id, tags: []) + hash = + params.require(:rule).permit(:channel_id, :type, :filter, :group_id, :category_id, tags: []) rule = DiscourseChatIntegration::Rule.new(hash) - if !rule.save - raise Discourse::InvalidParameters, 'Rule is not valid' - end + raise Discourse::InvalidParameters, "Rule is not valid" if !rule.save - render_serialized rule, DiscourseChatIntegration::RuleSerializer, root: 'rule' + render_serialized rule, DiscourseChatIntegration::RuleSerializer, root: "rule" rescue Discourse::InvalidParameters => e render json: { errors: [e.message] }, status: 422 end @@ -136,11 +137,9 @@ class DiscourseChatIntegration::ChatController < ApplicationController rule = DiscourseChatIntegration::Rule.find(params[:id].to_i) hash = params.require(:rule).permit(:type, :filter, :group_id, :category_id, tags: []) - if !rule.update(hash) - raise Discourse::InvalidParameters, 'Rule is not valid' - end + raise Discourse::InvalidParameters, "Rule is not valid" if !rule.update(hash) - render_serialized rule, DiscourseChatIntegration::RuleSerializer, root: 'rule' + render_serialized rule, DiscourseChatIntegration::RuleSerializer, root: "rule" rescue Discourse::InvalidParameters => e render json: { errors: [e.message] }, status: 422 end diff --git a/app/helpers/helper.rb b/app/helpers/helper.rb index 6aaf830..a2e4d44 100644 --- a/app/helpers/helper.rb +++ b/app/helpers/helper.rb @@ -2,7 +2,6 @@ module DiscourseChatIntegration module Helper - def self.process_command(channel, tokens) guardian = DiscourseChatIntegration::Manager.guardian @@ -16,13 +15,19 @@ module DiscourseChatIntegration when "thread", "watch", "follow", "mute" return error_text if tokens.empty? # If the first token in the command is a tag, this rule applies to all categories - category_name = tokens[0].start_with?('tag:') ? nil : tokens.shift + category_name = tokens[0].start_with?("tag:") ? nil : tokens.shift if category_name category = Category.find_by(slug: category_name) unless category - cat_list = (CategoryList.new(guardian).categories.map(&:slug)).join(', ') - return I18n.t("chat_integration.provider.#{provider}.not_found.category", name: category_name, list: cat_list) + cat_list = (CategoryList.new(guardian).categories.map(&:slug)).join(", ") + return( + I18n.t( + "chat_integration.provider.#{provider}.not_found.category", + name: category_name, + list: cat_list, + ) + ) end else category = nil # All categories @@ -32,8 +37,8 @@ module DiscourseChatIntegration # Every remaining token must be a tag. If not, abort and send help text while tokens.size > 0 token = tokens.shift - if token.start_with?('tag:') - tag_name = token.sub(/^tag:/, '') + if token.start_with?("tag:") + tag_name = token.sub(/^tag:/, "") else return error_text end @@ -47,7 +52,12 @@ module DiscourseChatIntegration end category_id = category.nil? ? nil : category.id - case DiscourseChatIntegration::Helper.smart_create_rule(channel: channel, filter: cmd, category_id: category_id, tags: tags) + case DiscourseChatIntegration::Helper.smart_create_rule( + channel: channel, + filter: cmd, + category_id: category_id, + tags: tags, + ) when :created I18n.t("chat_integration.provider.#{provider}.create.created") when :updated @@ -107,23 +117,25 @@ module DiscourseChatIntegration end end - text << I18n.t("chat_integration.provider.#{provider}.status.rule_string", + text << I18n.t( + "chat_integration.provider.#{provider}.status.rule_string", index: i, filter: rule.filter, - category: category_name + category: category_name, ) if SiteSetting.tagging_enabled && (!rule.tags.nil?) - text << I18n.t("chat_integration.provider.#{provider}.status.rule_string_tags_suffix", tags: rule.tags.join(', ')) + text << I18n.t( + "chat_integration.provider.#{provider}.status.rule_string_tags_suffix", + tags: rule.tags.join(", "), + ) end text << "\n" i += 1 end - if rules.size == 0 - text << I18n.t("chat_integration.provider.#{provider}.status.no_rules") - end + text << I18n.t("chat_integration.provider.#{provider}.status.no_rules") if rules.size == 0 text end @@ -144,12 +156,15 @@ module DiscourseChatIntegration # :created if a new rule has been created # false if there was an error def self.smart_create_rule(channel:, filter:, category_id: nil, tags: nil) - existing_rules = DiscourseChatIntegration::Rule.with_channel(channel).with_type('normal') + existing_rules = DiscourseChatIntegration::Rule.with_channel(channel).with_type("normal") # Select the ones that have the same category same_category = existing_rules.select { |rule| rule.category_id == category_id } - same_category_and_tags = same_category.select { |rule| (rule.tags.nil? ? [] : rule.tags.sort) == (tags.nil? ? [] : tags.sort) } + same_category_and_tags = + same_category.select do |rule| + (rule.tags.nil? ? [] : rule.tags.sort) == (tags.nil? ? [] : tags.sort) + end if same_category_and_tags.size > 0 # These rules have exactly the same criteria as what we're trying to create @@ -185,7 +200,9 @@ module DiscourseChatIntegration end # This rule is unique! Create a new one: - return :created if Rule.new(channel: channel, filter: filter, category_id: category_id, tags: tags).save + if Rule.new(channel: channel, filter: filter, category_id: category_id, tags: tags).save + return :created + end false end @@ -197,13 +214,13 @@ module DiscourseChatIntegration end def self.formatted_display_name(user) - if !SiteSetting.enable_names || user.name.blank? - return "@#{user.username}" - end + return "@#{user.username}" if !SiteSetting.enable_names || user.name.blank? full_name = user.name full_name_normalized = User.normalize_username(full_name.strip) - similar = full_name_normalized.gsub(' ', '_') == user.username_lower || full_name_normalized.gsub(' ', '') == user.username_lower + similar = + full_name_normalized.gsub(" ", "_") == user.username_lower || + full_name_normalized.gsub(" ", "") == user.username_lower if similar && SiteSetting.prioritize_username_in_ux? "@#{user.username}" elsif similar diff --git a/app/jobs/onceoff/add_type_field.rb b/app/jobs/onceoff/add_type_field.rb index a66e3d1..7254cb3 100644 --- a/app/jobs/onceoff/add_type_field.rb +++ b/app/jobs/onceoff/add_type_field.rb @@ -3,9 +3,7 @@ module Jobs class DiscourseChatAddTypeField < ::Jobs::Onceoff def execute_onceoff(args) - DiscourseChatIntegration::Rule.find_each do |rule| - rule.save(validate: false) - end + DiscourseChatIntegration::Rule.find_each { |rule| rule.save(validate: false) } end end end diff --git a/app/jobs/onceoff/migrate_from_slack_official.rb b/app/jobs/onceoff/migrate_from_slack_official.rb index e3eeda1..2a36a68 100644 --- a/app/jobs/onceoff/migrate_from_slack_official.rb +++ b/app/jobs/onceoff/migrate_from_slack_official.rb @@ -3,26 +3,26 @@ module Jobs class DiscourseChatMigrateFromSlackOfficial < ::Jobs::Onceoff def execute_onceoff(args) - slack_installed = PluginStoreRow.where(plugin_name: 'discourse-slack-official').exists? + slack_installed = PluginStoreRow.where(plugin_name: "discourse-slack-official").exists? if slack_installed - already_setup_rules = DiscourseChatIntegration::Channel.with_provider('slack').exists? + already_setup_rules = DiscourseChatIntegration::Channel.with_provider("slack").exists? already_setup_sitesettings = SiteSetting.chat_integration_slack_enabled || - SiteSetting.chat_integration_slack_access_token.present? || - SiteSetting.chat_integration_slack_incoming_webhook_token.present? || - SiteSetting.chat_integration_slack_outbound_webhook_url.present? + SiteSetting.chat_integration_slack_access_token.present? || + SiteSetting.chat_integration_slack_incoming_webhook_token.present? || + SiteSetting.chat_integration_slack_outbound_webhook_url.present? if !already_setup_rules && !already_setup_sitesettings ActiveRecord::Base.transaction do migrate_settings migrate_data - is_slack_enabled = site_settings_value('slack_enabled') + is_slack_enabled = site_settings_value("slack_enabled") if is_slack_enabled - slack_enabled = SiteSetting.find_by(name: 'slack_enabled') - slack_enabled.update!(value: 'f') + slack_enabled = SiteSetting.find_by(name: "slack_enabled") + slack_enabled.update!(value: "f") SiteSetting.chat_integration_slack_enabled = true SiteSetting.chat_integration_enabled = true @@ -30,44 +30,55 @@ module Jobs end end end - end def migrate_data rows = [] - PluginStoreRow.where(plugin_name: 'discourse-slack-official') + PluginStoreRow + .where(plugin_name: "discourse-slack-official") .where("key ~* :pat", pat: "^category_.*") .each do |row| + PluginStore + .cast_value(row.type_name, row.value) + .each do |rule| + category_id = + if row.key == "category_*" + nil + else + row.key.gsub!("category_", "") + row.key.to_i + end - PluginStore.cast_value(row.type_name, row.value).each do |rule| - category_id = - if row.key == 'category_*' - nil - else - row.key.gsub!('category_', '') - row.key.to_i + next if !category_id.nil? && !Category.exists?(id: category_id) + + valid_tags = [] + valid_tags = Tag.where(name: rule[:tags]).pluck(:name) if rule[:tags] + + rows << { + category_id: category_id, + channel: rule[:channel], + filter: rule[:filter], + tags: valid_tags, + } end - - next if !category_id.nil? && !Category.exists?(id: category_id) - - valid_tags = [] - valid_tags = Tag.where(name: rule[:tags]).pluck(:name) if rule[:tags] - - rows << { - category_id: category_id, - channel: rule[:channel], - filter: rule[:filter], - tags: valid_tags - } end - end rows.each do |row| # Load an existing channel with this identifier. If none, create it row[:channel] = "##{row[:channel]}" unless row[:channel].start_with?("#") - channel = DiscourseChatIntegration::Channel.with_provider('slack').with_data_value('identifier', row[:channel]).first + channel = + DiscourseChatIntegration::Channel + .with_provider("slack") + .with_data_value("identifier", row[:channel]) + .first if !channel - channel = DiscourseChatIntegration::Channel.create(provider: 'slack', data: { identifier: row[:channel] }) + channel = + DiscourseChatIntegration::Channel.create( + provider: "slack", + data: { + identifier: row[:channel], + }, + ) if !channel.id Rails.logger.warn("Error creating channel for #{row}") next @@ -75,50 +86,58 @@ module Jobs end # Create the rule, with clever logic for avoiding duplicates - success = DiscourseChatIntegration::Helper.smart_create_rule(channel: channel, filter: row[:filter], category_id: row[:category_id], tags: row[:tags]) + success = + DiscourseChatIntegration::Helper.smart_create_rule( + channel: channel, + filter: row[:filter], + category_id: row[:category_id], + tags: row[:tags], + ) end - end private def migrate_settings - if !(slack_access_token = site_settings_value('slack_access_token')).nil? + if !(slack_access_token = site_settings_value("slack_access_token")).nil? SiteSetting.chat_integration_slack_access_token = slack_access_token end - if !(slack_incoming_webhook_token = site_settings_value('slack_incoming_webhook_token')).nil? + if !(slack_incoming_webhook_token = site_settings_value("slack_incoming_webhook_token")).nil? SiteSetting.chat_integration_slack_incoming_webhook_token = slack_incoming_webhook_token end - if !(slack_discourse_excerpt_length = site_settings_value('slack_discourse_excerpt_length')).nil? + if !( + slack_discourse_excerpt_length = site_settings_value("slack_discourse_excerpt_length") + ).nil? SiteSetting.chat_integration_slack_excerpt_length = slack_discourse_excerpt_length end - if !(slack_outbound_webhook_url = site_settings_value('slack_outbound_webhook_url')).nil? + if !(slack_outbound_webhook_url = site_settings_value("slack_outbound_webhook_url")).nil? SiteSetting.chat_integration_slack_outbound_webhook_url = slack_outbound_webhook_url end - if !(slack_icon_url = site_settings_value('slack_icon_url')).nil? + if !(slack_icon_url = site_settings_value("slack_icon_url")).nil? SiteSetting.chat_integration_slack_icon_url = slack_icon_url end - if !(post_to_slack_window_secs = site_settings_value('post_to_slack_window_secs')).nil? + if !(post_to_slack_window_secs = site_settings_value("post_to_slack_window_secs")).nil? SiteSetting.chat_integration_delay_seconds = post_to_slack_window_secs end - if !(slack_discourse_username = site_settings_value('slack_discourse_username')).nil? + if !(slack_discourse_username = site_settings_value("slack_discourse_username")).nil? username = User.find_by(username: slack_discourse_username.downcase)&.username - SiteSetting.chat_integration_discourse_username = (username || Discourse.system_user.username) + SiteSetting.chat_integration_discourse_username = + (username || Discourse.system_user.username) end end def site_settings_value(name) value = SiteSetting.find_by(name: name)&.value - if value == 't' + if value == "t" value = true - elsif value == 'f' + elsif value == "f" value = false end diff --git a/app/models/channel.rb b/app/models/channel.rb index 312fd1a..7082360 100644 --- a/app/models/channel.rb +++ b/app/models/channel.rb @@ -2,10 +2,11 @@ class DiscourseChatIntegration::Channel < DiscourseChatIntegration::PluginModel # Setup ActiveRecord::Store to use the JSON field to read/write these values - store :value, accessors: [ :provider, :error_key, :error_info, :data ], coder: JSON + store :value, accessors: %i[provider error_key error_info data], coder: JSON scope :with_provider, ->(provider) { where("value::json->>'provider'=?", provider) } - scope :with_data_value, ->(key, value) { where("(value::json->>'data')::json->>?=?", key.to_s, value.to_s) } + scope :with_data_value, + ->(key, value) { where("(value::json->>'data')::json->>?=?", key.to_s, value.to_s) } after_initialize :init_data after_destroy :destroy_rules @@ -13,7 +14,7 @@ class DiscourseChatIntegration::Channel < DiscourseChatIntegration::PluginModel validate :provider_valid?, :data_valid? def self.key_prefix - 'channel:'.freeze + "channel:".freeze end def rules @@ -52,9 +53,7 @@ class DiscourseChatIntegration::Channel < DiscourseChatIntegration::PluginModel data.each do |key, value| regex_string = params.find { |p| p[:key] == key }[:regex] - if !Regexp.new(regex_string).match(value) - errors.add(:data, "data.#{key} is invalid") - end + errors.add(:data, "data.#{key} is invalid") if !Regexp.new(regex_string).match(value) unique = params.find { |p| p[:key] == key }[:unique] if unique @@ -63,8 +62,6 @@ class DiscourseChatIntegration::Channel < DiscourseChatIntegration::PluginModel end end - if check_unique && matching_channels.exists? - errors.add(:data, "matches an existing channel") - end + errors.add(:data, "matches an existing channel") if check_unique && matching_channels.exists? end end diff --git a/app/models/plugin_model.rb b/app/models/plugin_model.rb index 93e13de..06f5219 100644 --- a/app/models/plugin_model.rb +++ b/app/models/plugin_model.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class DiscourseChatIntegration::PluginModel < PluginStoreRow - PLUGIN_NAME = 'discourse-chat-integration' + PLUGIN_NAME = "discourse-chat-integration" default_scope { self.default_scope } @@ -9,13 +9,14 @@ class DiscourseChatIntegration::PluginModel < PluginStoreRow before_save :set_key def self.default_scope - where(type_name: 'JSON') - .where(plugin_name: self::PLUGIN_NAME) - .where("key LIKE ?", "#{self.key_prefix}%") + where(type_name: "JSON").where(plugin_name: self::PLUGIN_NAME).where( + "key LIKE ?", + "#{self.key_prefix}%", + ) end def self.key_prefix - raise 'Not implemented' + raise "Not implemented" end private @@ -25,7 +26,7 @@ class DiscourseChatIntegration::PluginModel < PluginStoreRow end def init_plugin_model - self.type_name ||= 'JSON' + self.type_name ||= "JSON" self.plugin_name ||= PLUGIN_NAME end @@ -37,5 +38,4 @@ class DiscourseChatIntegration::PluginModel < PluginStoreRow "#{self.key_prefix}#{max_id}" end end - end diff --git a/app/models/rule.rb b/app/models/rule.rb index 72e9fb5..275c9ba 100644 --- a/app/models/rule.rb +++ b/app/models/rule.rb @@ -2,54 +2,65 @@ class DiscourseChatIntegration::Rule < DiscourseChatIntegration::PluginModel # Setup ActiveRecord::Store to use the JSON field to read/write these values - store :value, accessors: [ :channel_id, :type, :group_id, :category_id, :tags, :filter ], coder: JSON + store :value, accessors: %i[channel_id type group_id category_id tags filter], coder: JSON scope :with_type, ->(type) { where("value::json->>'type'=?", type.to_s) } scope :with_channel, ->(channel) { with_channel_id(channel.id) } scope :with_channel_id, ->(channel_id) { where("value::json->>'channel_id'=?", channel_id.to_s) } - scope :with_category_id, ->(category_id) do - if category_id.nil? - where("(value::json->'category_id') IS NULL OR json_typeof(value::json->'category_id')='null'") - else - where("value::json->>'category_id'=?", category_id.to_s) - end - end + scope :with_category_id, + ->(category_id) { + if category_id.nil? + where( + "(value::json->'category_id') IS NULL OR json_typeof(value::json->'category_id')='null'", + ) + else + where("value::json->>'category_id'=?", category_id.to_s) + end + } - scope :with_group_ids, ->(group_id) do - where("value::json->>'group_id' IN (?)", group_id.map!(&:to_s)) - end + scope :with_group_ids, + ->(group_id) { where("value::json->>'group_id' IN (?)", group_id.map!(&:to_s)) } - scope :order_by_precedence, -> { - order(" + scope :order_by_precedence, + -> { + order( + " CASE WHEN value::json->>'type' = 'group_mention' THEN 1 WHEN value::json->>'type' = 'group_message' THEN 2 ELSE 3 END ", - " + " CASE WHEN value::json->>'filter' = 'mute' THEN 1 WHEN value::json->>'filter' = 'thread' THEN 2 WHEN value::json->>'filter' = 'watch' THEN 3 WHEN value::json->>'filter' = 'follow' THEN 4 END - ") - } + ", + ) + } after_initialize :init_filter - validates :filter, inclusion: { in: %w(thread watch follow mute), - message: "%{value} is not a valid filter" } + validates :filter, + inclusion: { + in: %w[thread watch follow mute], + message: "%{value} is not a valid filter", + } - validates :type, inclusion: { in: %w(normal group_message group_mention), - message: "%{value} is not a valid filter" } + validates :type, + inclusion: { + in: %w[normal group_message group_mention], + message: "%{value} is not a valid filter", + } validate :channel_valid?, :category_valid?, :group_valid?, :tags_valid? def self.key_prefix - 'rule:'.freeze + "rule:".freeze end # We never want an empty array, set it to nil instead @@ -62,7 +73,7 @@ class DiscourseChatIntegration::Rule < DiscourseChatIntegration::PluginModel end # These are only allowed to be integers - %w(channel_id category_id group_id).each do |name| + %w[channel_id category_id group_id].each do |name| define_method "#{name}=" do |val| if val.nil? || val.blank? super(nil) @@ -91,11 +102,11 @@ class DiscourseChatIntegration::Rule < DiscourseChatIntegration::PluginModel end def category_valid? - if type != 'normal' && !category_id.nil? + if type != "normal" && !category_id.nil? errors.add(:category_id, "cannot be specified for that type of rule") end - return unless type == 'normal' + return unless type == "normal" if !(category_id.nil? || Category.where(id: category_id).exists?) errors.add(:category_id, "#{category_id} is not a valid category id") @@ -103,11 +114,11 @@ class DiscourseChatIntegration::Rule < DiscourseChatIntegration::PluginModel end def group_valid? - if type == 'normal' && !group_id.nil? + if type == "normal" && !group_id.nil? errors.add(:group_id, "cannot be specified for that type of rule") end - return if type == 'normal' + return if type == "normal" if !Group.where(id: group_id).exists? errors.add(:group_id, "#{group_id} is not a valid group id") @@ -118,14 +129,12 @@ class DiscourseChatIntegration::Rule < DiscourseChatIntegration::PluginModel return if tags.nil? tags.each do |tag| - if !Tag.where(name: tag).exists? - errors.add(:tags, "#{tag} is not a valid tag") - end + errors.add(:tags, "#{tag} is not a valid tag") if !Tag.where(name: tag).exists? end end def init_filter - self.filter ||= 'watch' - self.type ||= 'normal' + self.filter ||= "watch" + self.type ||= "normal" end end diff --git a/app/routes/discourse.rb b/app/routes/discourse.rb index f9fb23d..8b42460 100644 --- a/app/routes/discourse.rb +++ b/app/routes/discourse.rb @@ -1,10 +1,13 @@ # frozen_string_literal: true Discourse::Application.routes.append do - mount ::DiscourseChatIntegration::AdminEngine, at: '/admin/plugins/chat-integration', constraints: AdminConstraint.new - mount ::DiscourseChatIntegration::PublicEngine, at: '/chat-transcript/', as: 'chat-transcript' - mount ::DiscourseChatIntegration::Provider::HookEngine, at: '/chat-integration/' + mount ::DiscourseChatIntegration::AdminEngine, + at: "/admin/plugins/chat-integration", + constraints: AdminConstraint.new + mount ::DiscourseChatIntegration::PublicEngine, at: "/chat-transcript/", as: "chat-transcript" + mount ::DiscourseChatIntegration::Provider::HookEngine, at: "/chat-integration/" # For backwards compatibility with Slack plugin - post "/slack/command" => "discourse_chat_integration/provider/slack_provider/slack_command#command" + post "/slack/command" => + "discourse_chat_integration/provider/slack_provider/slack_command#command" end diff --git a/app/routes/discourse_chat_integration.rb b/app/routes/discourse_chat_integration.rb index c21e561..86a98c5 100644 --- a/app/routes/discourse_chat_integration.rb +++ b/app/routes/discourse_chat_integration.rb @@ -1,26 +1,24 @@ # frozen_string_literal: true -require_dependency 'admin_constraint' +require_dependency "admin_constraint" module ::DiscourseChatIntegration AdminEngine.routes.draw do get "" => "chat#respond" - get '/providers' => "chat#list_providers" - post '/test' => "chat#test" + get "/providers" => "chat#list_providers" + post "/test" => "chat#test" - get '/channels' => "chat#list_channels" - post '/channels' => "chat#create_channel" - put '/channels/:id' => "chat#update_channel" - delete '/channels/:id' => "chat#destroy_channel" + get "/channels" => "chat#list_channels" + post "/channels" => "chat#create_channel" + put "/channels/:id" => "chat#update_channel" + delete "/channels/:id" => "chat#destroy_channel" - post '/rules' => "chat#create_rule" - put '/rules/:id' => "chat#update_rule" - delete '/rules/:id' => "chat#destroy_rule" + post "/rules" => "chat#create_rule" + put "/rules/:id" => "chat#update_rule" + delete "/rules/:id" => "chat#destroy_rule" get "/:provider" => "chat#respond" end - PublicEngine.routes.draw do - get '/:secret' => "public#post_transcript" - end + PublicEngine.routes.draw { get "/:secret" => "public#post_transcript" } end diff --git a/app/serializers/channel_serializer.rb b/app/serializers/channel_serializer.rb index e8e7113..15688c8 100644 --- a/app/serializers/channel_serializer.rb +++ b/app/serializers/channel_serializer.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative './rule_serializer' +require_relative "./rule_serializer" class DiscourseChatIntegration::ChannelSerializer < ApplicationSerializer attributes :id, :provider, :error_key, :error_info, :data, :rules diff --git a/app/services/manager.rb b/app/services/manager.rb index caf500d..a0c4e0c 100644 --- a/app/services/manager.rb +++ b/app/services/manager.rb @@ -2,7 +2,6 @@ module DiscourseChatIntegration module Manager - def self.guardian Guardian.new(User.find_by(username: SiteSetting.chat_integration_discourse_username)) end @@ -23,19 +22,26 @@ module DiscourseChatIntegration if topic.archetype == Archetype.private_message group_ids_with_access = topic.topic_allowed_groups.pluck(:group_id) return if group_ids_with_access.empty? - matching_rules = DiscourseChatIntegration::Rule.with_type('group_message').with_group_ids(group_ids_with_access) + matching_rules = + DiscourseChatIntegration::Rule.with_type("group_message").with_group_ids( + group_ids_with_access, + ) else - matching_rules = DiscourseChatIntegration::Rule.with_type('normal').with_category_id(topic.category_id) + matching_rules = + DiscourseChatIntegration::Rule.with_type("normal").with_category_id(topic.category_id) if topic.category # Also load the rules for the wildcard category - matching_rules += DiscourseChatIntegration::Rule.with_type('normal').with_category_id(nil) + matching_rules += DiscourseChatIntegration::Rule.with_type("normal").with_category_id(nil) end # If groups are mentioned, check for any matching rules and append them mentions = post.raw_mentions if mentions && mentions.length > 0 - groups = Group.where('LOWER(name) IN (?)', mentions) + groups = Group.where("LOWER(name) IN (?)", mentions) if groups.exists? - matching_rules += DiscourseChatIntegration::Rule.with_type('group_mention').with_group_ids(groups.map(&:id)) + matching_rules += + DiscourseChatIntegration::Rule.with_type("group_mention").with_group_ids( + groups.map(&:id), + ) end end end @@ -43,17 +49,19 @@ module DiscourseChatIntegration # If tagging is enabled, thow away rules that don't apply to this topic if SiteSetting.tagging_enabled topic_tags = topic.tags.present? ? topic.tags.pluck(:name) : [] - matching_rules = matching_rules.select do |rule| - next true if rule.tags.nil? || rule.tags.empty? # Filter has no tags specified - any_tags_match = !((rule.tags & topic_tags).empty?) - next any_tags_match # If any tags match, keep this filter, otherwise throw away - end + matching_rules = + matching_rules.select do |rule| + next true if rule.tags.nil? || rule.tags.empty? # Filter has no tags specified + any_tags_match = !((rule.tags & topic_tags).empty?) + next any_tags_match # If any tags match, keep this filter, otherwise throw away + end end # Sort by order of precedence - t_prec = { 'group_message' => 0, 'group_mention' => 1, 'normal' => 2 } # Group things win - f_prec = { 'mute' => 0, 'thread' => 1, 'watch' => 2, 'follow' => 3 } #(mute always wins; thread beats watch beats follow) - sort_func = proc { |a, b| [t_prec[a.type], f_prec[a.filter]] <=> [t_prec[b.type], f_prec[b.filter]] } + t_prec = { "group_message" => 0, "group_mention" => 1, "normal" => 2 } # Group things win + f_prec = { "mute" => 0, "thread" => 1, "watch" => 2, "follow" => 3 } #(mute always wins; thread beats watch beats follow) + sort_func = + proc { |a, b| [t_prec[a.type], f_prec[a.filter]] <=> [t_prec[b.type], f_prec[b.filter]] } matching_rules = matching_rules.sort(&sort_func) # Take the first rule for each channel @@ -81,14 +89,15 @@ module DiscourseChatIntegration begin provider.trigger_notification(post, channel, rule) - channel.update_attribute('error_key', nil) if channel.error_key + channel.update_attribute("error_key", nil) if channel.error_key rescue => e - if e.class == (DiscourseChatIntegration::ProviderError) && e.info.key?(:error_key) && !e.info[:error_key].nil? - channel.update_attribute('error_key', e.info[:error_key]) + if e.class == (DiscourseChatIntegration::ProviderError) && e.info.key?(:error_key) && + !e.info[:error_key].nil? + channel.update_attribute("error_key", e.info[:error_key]) else - channel.update_attribute('error_key', 'chat_integration.channel_exception') + channel.update_attribute("error_key", "chat_integration.channel_exception") end - channel.update_attribute('error_info', JSON.pretty_generate(e.try(:info))) + channel.update_attribute("error_info", JSON.pretty_generate(e.try(:info))) # Log the error # Discourse.handle_job_exception(e, @@ -99,10 +108,7 @@ module DiscourseChatIntegration # error_info: e.class == DiscourseChatIntegration::ProviderError ? e.info : nil } # ) end - end - end - end end diff --git a/db/migrate/20211202120030_add_unique_index_to_slack_thread_ts.rb b/db/migrate/20211202120030_add_unique_index_to_slack_thread_ts.rb index 15a6af1..a1d462b 100644 --- a/db/migrate/20211202120030_add_unique_index_to_slack_thread_ts.rb +++ b/db/migrate/20211202120030_add_unique_index_to_slack_thread_ts.rb @@ -2,9 +2,10 @@ class AddUniqueIndexToSlackThreadTs < ActiveRecord::Migration[6.1] def up - add_index :topic_custom_fields, [:topic_id, :name], - unique: true, - where: "(name LIKE 'slack_thread_id_%')", - name: "index_topic_custom_fields_on_topic_id_and_slack_thread_id" + add_index :topic_custom_fields, + %i[topic_id name], + unique: true, + where: "(name LIKE 'slack_thread_id_%')", + name: "index_topic_custom_fields_on_topic_id_and_slack_thread_id" end end diff --git a/lib/discourse_chat_integration/provider.rb b/lib/discourse_chat_integration/provider.rb index bb6859a..6251cd3 100644 --- a/lib/discourse_chat_integration/provider.rb +++ b/lib/discourse_chat_integration/provider.rb @@ -12,15 +12,11 @@ module DiscourseChatIntegration module Provider def self.providers - constants.select do |constant| - constant.to_s =~ /Provider$/ - end.map(&method(:const_get)) + constants.select { |constant| constant.to_s =~ /Provider$/ }.map(&method(:const_get)) end def self.enabled_providers - self.providers.select do |provider| - self.is_enabled(provider) - end + self.providers.select { |provider| self.is_enabled(provider) } end def self.provider_names @@ -36,7 +32,7 @@ module DiscourseChatIntegration end def self.is_enabled(provider) - if defined? provider::PROVIDER_ENABLED_SETTING + if defined?(provider::PROVIDER_ENABLED_SETTING) SiteSetting.public_send(provider::PROVIDER_ENABLED_SETTING) else false @@ -51,9 +47,10 @@ module DiscourseChatIntegration class HookController < ::ApplicationController requires_plugin DiscourseChatIntegration::PLUGIN_NAME - class ProviderDisabled < StandardError; end + class ProviderDisabled < StandardError + end - rescue_from ProviderDisabled do + rescue_from ProviderDisabled do rescue_discourse_actions(:not_found, 404) end @@ -72,22 +69,20 @@ module DiscourseChatIntegration def self.mount_engines engines = [] DiscourseChatIntegration::Provider.providers.each do |provider| - engine = provider.constants.select do |constant| - constant.to_s =~ (/Engine$/) && (constant.to_s != "HookEngine") - end.map(&provider.method(:const_get)).first + engine = + provider + .constants + .select { |constant| constant.to_s =~ (/Engine$/) && (constant.to_s != "HookEngine") } + .map(&provider.method(:const_get)) + .first - if engine - engines.push(engine: engine, name: provider::PROVIDER_NAME) - end + engines.push(engine: engine, name: provider::PROVIDER_NAME) if engine end DiscourseChatIntegration::Provider::HookEngine.routes.draw do - engines.each do |engine| - mount engine[:engine], at: engine[:name] - end + engines.each { |engine| mount engine[:engine], at: engine[:name] } end end - end end diff --git a/lib/discourse_chat_integration/provider/discord/discord_provider.rb b/lib/discourse_chat_integration/provider/discord/discord_provider.rb index 8788b34..85b9532 100644 --- a/lib/discourse_chat_integration/provider/discord/discord_provider.rb +++ b/lib/discourse_chat_integration/provider/discord/discord_provider.rb @@ -8,7 +8,12 @@ module DiscourseChatIntegration CHANNEL_PARAMETERS = [ { key: "name", regex: '^\S+' }, - { key: "webhook_url", regex: '^https:\/\/discord\.com\/api\/webhooks\/', unique: true, hidden: true } + { + key: "webhook_url", + regex: '^https:\/\/discord\.com\/api\/webhooks\/', + unique: true, + hidden: true, + }, ].freeze def self.send_message(url, message) @@ -17,7 +22,7 @@ module DiscourseChatIntegration uri = URI(url) - req = Net::HTTP::Post.new(uri, 'Content-Type' => 'application/json') + req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json") req.body = message.to_json response = http.request(req) @@ -25,7 +30,7 @@ module DiscourseChatIntegration end def self.ensure_protocol(url) - return url if !url.start_with?('//') + return url if !url.start_with?("//") "http:#{url}" end @@ -34,24 +39,40 @@ module DiscourseChatIntegration topic = post.topic - category = '' + category = "" if topic.category - category = (topic.category.parent_category) ? "[#{topic.category.parent_category.name}/#{topic.category.name}]" : "[#{topic.category.name}]" + category = + ( + if (topic.category.parent_category) + "[#{topic.category.parent_category.name}/#{topic.category.name}]" + else + "[#{topic.category.name}]" + end + ) end message = { content: SiteSetting.chat_integration_discord_message_content, - embeds: [{ - title: "#{topic.title} #{(category == '[uncategorized]') ? '' : category} #{topic.tags.present? ? topic.tags.map(&:name).join(', ') : ''}", - color: topic.category ? topic.category.color.to_i(16) : nil, - description: post.excerpt(SiteSetting.chat_integration_discord_excerpt_length, text_entities: true, strip_links: true, remap_emoji: true), - url: post.full_url, - author: { - name: display_name, - url: Discourse.base_url + "/u/" + post.user.username, - icon_url: ensure_protocol(post.user.small_avatar_url) - } - }] + embeds: [ + { + title: + "#{topic.title} #{(category == "[uncategorized]") ? "" : category} #{topic.tags.present? ? topic.tags.map(&:name).join(", ") : ""}", + color: topic.category ? topic.category.color.to_i(16) : nil, + description: + post.excerpt( + SiteSetting.chat_integration_discord_excerpt_length, + text_entities: true, + strip_links: true, + remap_emoji: true, + ), + url: post.full_url, + author: { + name: display_name, + url: Discourse.base_url + "/u/" + post.user.username, + icon_url: ensure_protocol(post.user.small_avatar_url), + }, + }, + ], } message @@ -59,17 +80,20 @@ module DiscourseChatIntegration def self.trigger_notification(post, channel, rule) # Adding ?wait=true means that we actually get a success/failure response, rather than returning asynchronously - webhook_url = "#{channel.data['webhook_url']}?wait=true" + webhook_url = "#{channel.data["webhook_url"]}?wait=true" message = generate_discord_message(post) response = send_message(webhook_url, message) if !response.kind_of?(Net::HTTPSuccess) - raise ::DiscourseChatIntegration::ProviderError.new(info: { - error_key: nil, message: message, response_body: response.body - }) + raise ::DiscourseChatIntegration::ProviderError.new( + info: { + error_key: nil, + message: message, + response_body: response.body, + }, + ) end end - end end end diff --git a/lib/discourse_chat_integration/provider/flowdock/flowdock_provider.rb b/lib/discourse_chat_integration/provider/flowdock/flowdock_provider.rb index 690e54f..c1386b5 100644 --- a/lib/discourse_chat_integration/provider/flowdock/flowdock_provider.rb +++ b/lib/discourse_chat_integration/provider/flowdock/flowdock_provider.rb @@ -1,12 +1,9 @@ # frozen_string_literal: true module DiscourseChatIntegration::Provider::FlowdockProvider - PROVIDER_NAME = "flowdock".freeze PROVIDER_ENABLED_SETTING = :chat_integration_flowdock_enabled - CHANNEL_PARAMETERS = [ - { key: "flow_token", regex: '^\S+', unique: true, hidden: true }, - ] + CHANNEL_PARAMETERS = [{ key: "flow_token", regex: '^\S+', unique: true, hidden: true }] def self.send_message(url, message) uri = URI(url) @@ -14,7 +11,7 @@ module DiscourseChatIntegration::Provider::FlowdockProvider http = FinalDestination::HTTP.new(uri.host, uri.port) http.use_ssl = true - req = Net::HTTP::Post.new(uri, 'Content-Type' => 'application/json') + req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json") req.body = message.to_json response = http.request(req) @@ -29,15 +26,21 @@ module DiscourseChatIntegration::Provider::FlowdockProvider event: "discussion", author: { name: display_name, - avatar: post.user.small_avatar_url + avatar: post.user.small_avatar_url, }, title: I18n.t("chat_integration.provider.flowdock.message_title"), external_thread_id: post.topic.id, - body: post.excerpt(SiteSetting.chat_integration_flowdock_excerpt_length, text_entities: true, strip_links: false, remap_emoji: true), + body: + post.excerpt( + SiteSetting.chat_integration_flowdock_excerpt_length, + text_entities: true, + strip_links: false, + remap_emoji: true, + ), thread: { title: post.topic.title, - external_url: post.full_url - } + external_url: post.full_url, + }, } message @@ -50,7 +53,11 @@ module DiscourseChatIntegration::Provider::FlowdockProvider unless response.kind_of?(Net::HTTPSuccess) error_key = nil - raise ::DiscourseChatIntegration::ProviderError.new info: { error_key: error_key, message: message, response_body: response.body } + raise ::DiscourseChatIntegration::ProviderError.new info: { + error_key: error_key, + message: message, + response_body: response.body, + } end end end diff --git a/lib/discourse_chat_integration/provider/gitter/gitter_provider.rb b/lib/discourse_chat_integration/provider/gitter/gitter_provider.rb index cf2cbc7..04da4a5 100644 --- a/lib/discourse_chat_integration/provider/gitter/gitter_provider.rb +++ b/lib/discourse_chat_integration/provider/gitter/gitter_provider.rb @@ -3,19 +3,28 @@ module DiscourseChatIntegration module Provider module GitterProvider - PROVIDER_NAME = 'gitter'.freeze + PROVIDER_NAME = "gitter".freeze PROVIDER_ENABLED_SETTING = :chat_integration_gitter_enabled CHANNEL_PARAMETERS = [ { key: "name", regex: '^\S+$', unique: true }, - { key: "webhook_url", regex: '^https://webhooks\.gitter\.im/e/\S+$', unique: true, hidden: true } + { + key: "webhook_url", + regex: '^https://webhooks\.gitter\.im/e/\S+$', + unique: true, + hidden: true, + }, ] def self.trigger_notification(post, channel, rule) message = gitter_message(post) - response = Net::HTTP.post_form(URI(channel.data['webhook_url']), message: message) + response = Net::HTTP.post_form(URI(channel.data["webhook_url"]), message: message) unless response.kind_of? Net::HTTPSuccess error_key = nil - raise ::DiscourseChatIntegration::ProviderError.new info: { error_key: error_key, message: message, response_body: response.body } + raise ::DiscourseChatIntegration::ProviderError.new info: { + error_key: error_key, + message: message, + response_body: response.body, + } end end @@ -23,7 +32,14 @@ module DiscourseChatIntegration display_name = post.user.username topic = post.topic parent_category = topic.category.try :parent_category - category_name = parent_category ? "[#{parent_category.name}/#{topic.category.name}]" : "[#{topic.category.name}]" + category_name = + ( + if parent_category + "[#{parent_category.name}/#{topic.category.name}]" + else + "[#{topic.category.name}]" + end + ) "[__#{display_name}__ - #{topic.title} - #{category_name}](#{post.full_url})" end diff --git a/lib/discourse_chat_integration/provider/google/google_provider.rb b/lib/discourse_chat_integration/provider/google/google_provider.rb index c0a47cb..ec223b8 100644 --- a/lib/discourse_chat_integration/provider/google/google_provider.rb +++ b/lib/discourse_chat_integration/provider/google/google_provider.rb @@ -3,26 +3,35 @@ module DiscourseChatIntegration module Provider module GoogleProvider - PROVIDER_NAME = 'google'.freeze + PROVIDER_NAME = "google".freeze PROVIDER_ENABLED_SETTING = :chat_integration_google_enabled CHANNEL_PARAMETERS = [ { key: "name", regex: '^\S+$', unique: true }, - { key: "webhook_url", regex: '^https:\/\/chat.googleapis.com\/v1\/\S+$', unique: true, hidden: true } + { + key: "webhook_url", + regex: '^https:\/\/chat.googleapis.com\/v1\/\S+$', + unique: true, + hidden: true, + }, ] def self.trigger_notification(post, channel, rule) message = get_message(post) - uri = URI(channel.data['webhook_url']) + uri = URI(channel.data["webhook_url"]) http = FinalDestination::HTTP.new(uri.host, uri.port) - http.use_ssl = (uri.scheme == 'https') + http.use_ssl = (uri.scheme == "https") - req = Net::HTTP::Post.new(uri, 'Content-Type' => 'application/json') + req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json") req.body = message.to_json response = http.request(req) unless response.kind_of? Net::HTTPSuccess - raise ::DiscourseChatIntegration::ProviderError.new info: { request: req.body, response_code: response.code, response_body: response.body } + raise ::DiscourseChatIntegration::ProviderError.new info: { + request: req.body, + response_code: response.code, + response_body: response.body, + } end end @@ -35,49 +44,67 @@ module DiscourseChatIntegration widgets: [ { keyValue: { - "topLabel": I18n.t("chat_integration.provider.google.new_#{post.is_first_post? ? "topic" : "post"}", site_title: SiteSetting.title), - "content": post.topic.title, - "contentMultiline": "false", - "bottomLabel": I18n.t("chat_integration.provider.google.author", username: post.user.username), - "onClick": { - "openLink": { - "url": post.full_url - } - } - } + topLabel: + I18n.t( + "chat_integration.provider.google.new_#{post.is_first_post? ? "topic" : "post"}", + site_title: SiteSetting.title, + ), + content: post.topic.title, + contentMultiline: "false", + bottomLabel: + I18n.t( + "chat_integration.provider.google.author", + username: post.user.username, + ), + onClick: { + openLink: { + url: post.full_url, + }, + }, + }, }, - ] + ], }, { widgets: [ { textParagraph: { - text: post.excerpt(SiteSetting.chat_integration_google_excerpt_length, text_entities: true, strip_links: true, remap_emoji: true) - } + text: + post.excerpt( + SiteSetting.chat_integration_google_excerpt_length, + text_entities: true, + strip_links: true, + remap_emoji: true, + ), + }, }, - ] + ], }, { widgets: [ { buttons: [ { - "textButton": { - "text": I18n.t("chat_integration.provider.google.link", site_title: SiteSetting.title), - "onClick": { - "openLink": { - "url": post.full_url - } - } - } + textButton: { + text: + I18n.t( + "chat_integration.provider.google.link", + site_title: SiteSetting.title, + ), + onClick: { + openLink: { + url: post.full_url, + }, + }, + }, }, - ] - } - ] + ], + }, + ], }, ], - } - ] + }, + ], } end end diff --git a/lib/discourse_chat_integration/provider/groupme/groupme_provider.rb b/lib/discourse_chat_integration/provider/groupme/groupme_provider.rb index 7efad34..797afb0 100644 --- a/lib/discourse_chat_integration/provider/groupme/groupme_provider.rb +++ b/lib/discourse_chat_integration/provider/groupme/groupme_provider.rb @@ -2,27 +2,32 @@ module DiscourseChatIntegration::Provider::GroupmeProvider PROVIDER_NAME = "groupme".freeze PROVIDER_ENABLED_SETTING = :chat_integration_groupme_enabled - CHANNEL_PARAMETERS = [ - { key: "groupme_instance_name", regex: '[\s\S]*', unique: true } - ] + CHANNEL_PARAMETERS = [{ key: "groupme_instance_name", regex: '[\s\S]*', unique: true }] def self.generate_groupme_message(post) display_name = ::DiscourseChatIntegration::Helper.formatted_display_name(post.user) topic = post.topic - category = '' + category = "" if topic.category&.uncategorized? - category = "#{I18n.t('uncategorized_category_name')}" + category = "#{I18n.t("uncategorized_category_name")}" elsif topic.category - category = (topic.category.parent_category) ? "#{topic.category.parent_category.name}/#{topic.category.name}" : "#{topic.category.name}" + category = + ( + if (topic.category.parent_category) + "#{topic.category.parent_category.name}/#{topic.category.name}" + else + "#{topic.category.name}" + end + ) end - pre_post_text = "#{display_name} Posted to #{SiteSetting.title}\n\nTopic: #{topic.title} [#{category}]" + pre_post_text = + "#{display_name} Posted to #{SiteSetting.title}\n\nTopic: #{topic.title} [#{category}]" read_more = "(Read More: #{post.full_url})" - post_excerpt = "#{post.excerpt(SiteSetting.chat_integration_groupme_excerpt_length, text_entities: true, strip_links: true, remap_emoji: true)}" - data = { - text: "#{pre_post_text}\n\n#{post_excerpt}\n#{read_more}" - } + post_excerpt = + "#{post.excerpt(SiteSetting.chat_integration_groupme_excerpt_length, text_entities: true, strip_links: true, remap_emoji: true)}" + data = { text: "#{pre_post_text}\n\n#{post_excerpt}\n#{read_more}" } data end @@ -35,33 +40,39 @@ module DiscourseChatIntegration::Provider::GroupmeProvider instance_names = SiteSetting.chat_integration_groupme_instance_names.split(/\s*,\s*/) unless instance_names.length() == bot_ids.length() - instance_names = [I18n.t('chat_integration.provider.groupme.errors.instance_names_issue')] * bot_ids.length() + instance_names = + [I18n.t("chat_integration.provider.groupme.errors.instance_names_issue")] * bot_ids.length() end name_to_id = Hash[instance_names.zip(bot_ids)] - user_input_channel = channel.data['groupme_instance_name'].strip - unless user_input_channel.eql? 'all' - instance_names = [user_input_channel] - end - instance_names.each { |instance_name| + user_input_channel = channel.data["groupme_instance_name"].strip + instance_names = [user_input_channel] unless user_input_channel.eql? "all" + instance_names.each do |instance_name| bot_id = name_to_id["#{instance_name}"] uri = URI("https://api.groupme.com/v3/bots/post") http = FinalDestination::HTTP.new(uri.host, uri.port) - http.use_ssl = (uri.scheme == 'https') - req = Net::HTTP::Post.new(uri, 'Content-Type' => 'application/json') + http.use_ssl = (uri.scheme == "https") + req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json") message[:bot_id] = bot_id req.body = message.to_json response = http.request(req) unless response.kind_of? Net::HTTPSuccess num_errors += 1 - if response.code.to_s == '404' - error_key = 'chat_integration.provider.groupme.errors.not_found' + if response.code.to_s == "404" + error_key = "chat_integration.provider.groupme.errors.not_found" else error_key = nil end - last_error_raised = { error_key: error_key, groupme_name: instance_name, bot_id: bot_id, request: req.body, response_code: response.code, response_body: response.body } + last_error_raised = { + error_key: error_key, + groupme_name: instance_name, + bot_id: bot_id, + request: req.body, + response_code: response.code, + response_body: response.body, + } end - } + end if last_error_raised successfully_sent = instance_names.length() - num_errors last_error_raised[:success_rate] = "#{successfully_sent}/#{instance_names.length()}" diff --git a/lib/discourse_chat_integration/provider/guilded/guilded_provider.rb b/lib/discourse_chat_integration/provider/guilded/guilded_provider.rb index 73da311..d50ae0c 100644 --- a/lib/discourse_chat_integration/provider/guilded/guilded_provider.rb +++ b/lib/discourse_chat_integration/provider/guilded/guilded_provider.rb @@ -7,27 +7,43 @@ module DiscourseChatIntegration PROVIDER_ENABLED_SETTING = :chat_integration_guilded_enabled CHANNEL_PARAMETERS = [ - { key: "name", regex: '^\S+' }, - { key: "webhook_url", regex: '^https:\/\/media\.guilded\.gg\/webhooks\/', unique: true, hidden: true } + { key: "name", regex: '^\S+' }, + { + key: "webhook_url", + regex: '^https:\/\/media\.guilded\.gg\/webhooks\/', + unique: true, + hidden: true, + }, ].freeze def self.trigger_notification(post, channel, rule) - webhook_url = channel.data['webhook_url'] + webhook_url = channel.data["webhook_url"] message = generate_guilded_message(post) response = send_message(webhook_url, message) if !response.kind_of?(Net::HTTPSuccess) - raise ::DiscourseChatIntegration::ProviderError.new(info: { - error_key: nil, message: message, response_body: response.body - }) + raise ::DiscourseChatIntegration::ProviderError.new( + info: { + error_key: nil, + message: message, + response_body: response.body, + }, + ) end end def self.generate_guilded_message(post) topic = post.topic - category = '' + category = "" if topic.category - category = (topic.category.parent_category) ? "[#{topic.category.parent_category.name}/#{topic.category.name}]" : "[#{topic.category.name}]" + category = + ( + if (topic.category.parent_category) + "[#{topic.category.parent_category.name}/#{topic.category.name}]" + else + "[#{topic.category.name}]" + end + ) end display_name = ::DiscourseChatIntegration::Helper.formatted_display_name(post.user) @@ -37,15 +53,24 @@ module DiscourseChatIntegration end message = { - embeds: [{ - title: "#{topic.title} #{(category == '[uncategorized]') ? '' : category} #{topic.tags.present? ? topic.tags.map(&:name).join(', ') : ''}", - url: post.full_url, - description: post.excerpt(SiteSetting.chat_integration_guilded_excerpt_length, text_entities: true, strip_links: true, remap_emoji: true), - footer: { - icon_url: ensure_protocol(post.user.small_avatar_url), - text: "#{display_name} | #{post.created_at}" - } - }] + embeds: [ + { + title: + "#{topic.title} #{(category == "[uncategorized]") ? "" : category} #{topic.tags.present? ? topic.tags.map(&:name).join(", ") : ""}", + url: post.full_url, + description: + post.excerpt( + SiteSetting.chat_integration_guilded_excerpt_length, + text_entities: true, + strip_links: true, + remap_emoji: true, + ), + footer: { + icon_url: ensure_protocol(post.user.small_avatar_url), + text: "#{display_name} | #{post.created_at}", + }, + }, + ], } message @@ -54,9 +79,9 @@ module DiscourseChatIntegration def self.send_message(url, message) uri = URI(url) http = FinalDestination::HTTP.new(uri.host, uri.port) - http.use_ssl = (uri.scheme == 'https') + http.use_ssl = (uri.scheme == "https") - req = Net::HTTP::Post.new(uri, 'Content-Type' => 'application/json') + req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json") req.body = message.to_json response = http.request(req) @@ -64,10 +89,9 @@ module DiscourseChatIntegration end def self.ensure_protocol(url) - return url if !url.start_with?('//') + return url if !url.start_with?("//") "http:#{url}" end - end end end diff --git a/lib/discourse_chat_integration/provider/matrix/matrix_provider.rb b/lib/discourse_chat_integration/provider/matrix/matrix_provider.rb index b26f4db..612d81b 100644 --- a/lib/discourse_chat_integration/provider/matrix/matrix_provider.rb +++ b/lib/discourse_chat_integration/provider/matrix/matrix_provider.rb @@ -6,25 +6,27 @@ module DiscourseChatIntegration PROVIDER_NAME = "matrix".freeze PROVIDER_ENABLED_SETTING = :chat_integration_matrix_enabled CHANNEL_PARAMETERS = [ - { key: "name", regex: '^\S+' }, - { key: "room_id", regex: '^\!\S+:\S+$', unique: true, hidden: true } - ] + { key: "name", regex: '^\S+' }, + { key: "room_id", regex: '^\!\S+:\S+$', unique: true, hidden: true }, + ] def self.send_message(room_id, message) homeserver = SiteSetting.chat_integration_matrix_homeserver - event_type = 'm.room.message' + event_type = "m.room.message" uid = Time.now.to_i - url_params = URI.encode_www_form(access_token: SiteSetting.chat_integration_matrix_access_token) + url_params = + URI.encode_www_form(access_token: SiteSetting.chat_integration_matrix_access_token) - url = "#{homeserver}/_matrix/client/r0/rooms/#{CGI::escape(room_id)}/send/#{event_type}/#{uid}" + url = + "#{homeserver}/_matrix/client/r0/rooms/#{CGI.escape(room_id)}/send/#{event_type}/#{uid}" - uri = URI([url, url_params].join('?')) + uri = URI([url, url_params].join("?")) http = FinalDestination::HTTP.new(uri.host, uri.port) http.use_ssl = true - req = Net::HTTP::Put.new(uri, 'Content-Type' => 'application/json') + req = Net::HTTP::Put.new(uri, "Content-Type" => "application/json") req.body = message.to_json response = http.request(req) @@ -35,16 +37,29 @@ module DiscourseChatIntegration display_name = ::DiscourseChatIntegration::Helper.formatted_display_name(post.user) message = { - msgtype: SiteSetting.chat_integration_matrix_use_notice ? 'm.notice' : 'm.text', - body: I18n.t('chat_integration.provider.matrix.text_message', user: display_name, - post_url: post.full_url, - title: post.topic.title), - format: 'org.matrix.custom.html', - formatted_body: I18n.t('chat_integration.provider.matrix.formatted_message', user: display_name, - post_url: post.full_url, - title: post.topic.title, - excerpt: post.excerpt(SiteSetting.chat_integration_matrix_excerpt_length, text_entities: true, strip_links: true, remap_emoji: true)) - + msgtype: SiteSetting.chat_integration_matrix_use_notice ? "m.notice" : "m.text", + body: + I18n.t( + "chat_integration.provider.matrix.text_message", + user: display_name, + post_url: post.full_url, + title: post.topic.title, + ), + format: "org.matrix.custom.html", + formatted_body: + I18n.t( + "chat_integration.provider.matrix.formatted_message", + user: display_name, + post_url: post.full_url, + title: post.topic.title, + excerpt: + post.excerpt( + SiteSetting.chat_integration_matrix_excerpt_length, + text_entities: true, + strip_links: true, + remap_emoji: true, + ), + ), } message @@ -53,24 +68,26 @@ module DiscourseChatIntegration def self.trigger_notification(post, channel, rule) message = generate_matrix_message(post) - response = send_message(channel.data['room_id'], message) + response = send_message(channel.data["room_id"], message) if !response.kind_of?(Net::HTTPSuccess) error_key = nil begin responseData = JSON.parse(response.body) - if responseData['errcode'] == "M_UNKNOWN_TOKEN" - error_key = 'chat_integration.provider.matrix.errors.unknown_token' - elsif responseData['errcode'] == "M_UNKNOWN" - error_key = 'chat_integration.provider.matrix.errors.unknown_room' + if responseData["errcode"] == "M_UNKNOWN_TOKEN" + error_key = "chat_integration.provider.matrix.errors.unknown_token" + elsif responseData["errcode"] == "M_UNKNOWN" + error_key = "chat_integration.provider.matrix.errors.unknown_room" end ensure - raise ::DiscourseChatIntegration::ProviderError.new info: { error_key: error_key, message: message, response_body: response.body } + raise ::DiscourseChatIntegration::ProviderError.new info: { + error_key: error_key, + message: message, + response_body: response.body, + } end end - end - end end end diff --git a/lib/discourse_chat_integration/provider/mattermost/mattermost_command_controller.rb b/lib/discourse_chat_integration/provider/mattermost/mattermost_command_controller.rb index 307632f..64549a2 100644 --- a/lib/discourse_chat_integration/provider/mattermost/mattermost_command_controller.rb +++ b/lib/discourse_chat_integration/provider/mattermost/mattermost_command_controller.rb @@ -15,22 +15,18 @@ module DiscourseChatIntegration::Provider::MattermostProvider def command text = process_command(params) - render json: { - response_type: 'ephemeral', - text: text - } + render json: { response_type: "ephemeral", text: text } end def process_command(params) - tokens = params[:text].split(" ") # channel name fix channel_id = case params[:channel_name] - when 'directmessage' + when "directmessage" "@#{params[:user_name]}" - when 'privategroup' + when "privategroup" params[:channel_id] else "##{params[:channel_name]}" @@ -38,21 +34,29 @@ module DiscourseChatIntegration::Provider::MattermostProvider provider = DiscourseChatIntegration::Provider::MattermostProvider::PROVIDER_NAME - channel = DiscourseChatIntegration::Channel.with_provider(provider).with_data_value('identifier', channel_id).first + channel = + DiscourseChatIntegration::Channel + .with_provider(provider) + .with_data_value("identifier", channel_id) + .first # Create channel if doesn't exist - channel ||= DiscourseChatIntegration::Channel.create!(provider: provider, data: { identifier: channel_id }) + channel ||= + DiscourseChatIntegration::Channel.create!( + provider: provider, + data: { + identifier: channel_id, + }, + ) ::DiscourseChatIntegration::Helper.process_command(channel, tokens) - end def mattermost_token_valid? params.require(:token) if SiteSetting.chat_integration_mattermost_incoming_webhook_token.blank? || - SiteSetting.chat_integration_mattermost_incoming_webhook_token != params[:token] - + SiteSetting.chat_integration_mattermost_incoming_webhook_token != params[:token] raise Discourse::InvalidAccess.new end end @@ -63,8 +67,5 @@ module DiscourseChatIntegration::Provider::MattermostProvider isolate_namespace DiscourseChatIntegration::Provider::MattermostProvider end - MattermostEngine.routes.draw do - post "command" => "mattermost_command#command" - end - + MattermostEngine.routes.draw { post "command" => "mattermost_command#command" } end diff --git a/lib/discourse_chat_integration/provider/mattermost/mattermost_provider.rb b/lib/discourse_chat_integration/provider/mattermost/mattermost_provider.rb index a95923b..e75aa91 100644 --- a/lib/discourse_chat_integration/provider/mattermost/mattermost_provider.rb +++ b/lib/discourse_chat_integration/provider/mattermost/mattermost_provider.rb @@ -5,29 +5,30 @@ module DiscourseChatIntegration module MattermostProvider PROVIDER_NAME = "mattermost".freeze PROVIDER_ENABLED_SETTING = :chat_integration_mattermost_enabled - CHANNEL_PARAMETERS = [ - { key: "identifier", regex: '^[@#]\S*$', unique: true } - ] + CHANNEL_PARAMETERS = [{ key: "identifier", regex: '^[@#]\S*$', unique: true }] def self.send_via_webhook(message) - uri = URI(SiteSetting.chat_integration_mattermost_webhook_url) http = FinalDestination::HTTP.new(uri.host, uri.port) - http.use_ssl = (uri.scheme == 'https') - req = Net::HTTP::Post.new(uri, 'Content-Type' => 'application/json') + http.use_ssl = (uri.scheme == "https") + req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json") req.body = message.to_json response = http.request(req) unless response.kind_of? Net::HTTPSuccess if response.body.include? "Couldn't find the channel" - error_key = 'chat_integration.provider.mattermost.errors.channel_not_found' + error_key = "chat_integration.provider.mattermost.errors.channel_not_found" else error_key = nil end - raise ::DiscourseChatIntegration::ProviderError.new info: { error_key: error_key, request: req.body, response_code: response.code, response_body: response.body } + raise ::DiscourseChatIntegration::ProviderError.new info: { + error_key: error_key, + request: req.body, + response_code: response.code, + response_body: response.body, + } end - end def self.mattermost_message(post, channel) @@ -35,17 +36,26 @@ module DiscourseChatIntegration topic = post.topic - category = '' + category = "" if topic.category&.uncategorized? - category = "[#{I18n.t('uncategorized_category_name')}]" + category = "[#{I18n.t("uncategorized_category_name")}]" elsif topic.category - category = (topic.category.parent_category) ? "[#{topic.category.parent_category.name}/#{topic.category.name}]" : "[#{topic.category.name}]" + category = + ( + if (topic.category.parent_category) + "[#{topic.category.parent_category.name}/#{topic.category.name}]" + else + "[#{topic.category.name}]" + end + ) end icon_url = if SiteSetting.chat_integration_mattermost_icon_url.present? UrlHelper.absolute(SiteSetting.chat_integration_mattermost_icon_url) - elsif (url = (SiteSetting.try(:site_logo_small_url) || SiteSetting.logo_small_url)).present? + elsif ( + url = (SiteSetting.try(:site_logo_small_url) || SiteSetting.logo_small_url) + ).present? UrlHelper.absolute(url) end @@ -53,7 +63,7 @@ module DiscourseChatIntegration channel: channel, username: SiteSetting.title || "Discourse", icon_url: icon_url, - attachments: [] + attachments: [], } summary = { @@ -61,8 +71,15 @@ module DiscourseChatIntegration author_name: display_name, author_icon: post.user.small_avatar_url, color: topic.category ? "##{topic.category.color}" : nil, - text: post.excerpt(SiteSetting.chat_integration_mattermost_excerpt_length, text_entities: true, strip_links: true, remap_emoji: true), - title: "#{topic.title} #{category} #{topic.tags.present? ? topic.tags.map(&:name).join(', ') : ''}", + text: + post.excerpt( + SiteSetting.chat_integration_mattermost_excerpt_length, + text_entities: true, + strip_links: true, + remap_emoji: true, + ), + title: + "#{topic.title} #{category} #{topic.tags.present? ? topic.tags.map(&:name).join(", ") : ""}", title_link: post.full_url, } @@ -71,12 +88,11 @@ module DiscourseChatIntegration end def self.trigger_notification(post, channel, rule) - channel_id = channel.data['identifier'] + channel_id = channel.data["identifier"] message = mattermost_message(post, channel_id) self.send_via_webhook(message) end - end end end diff --git a/lib/discourse_chat_integration/provider/rocketchat/rocketchat_provider.rb b/lib/discourse_chat_integration/provider/rocketchat/rocketchat_provider.rb index fad7ffa..79ba665 100644 --- a/lib/discourse_chat_integration/provider/rocketchat/rocketchat_provider.rb +++ b/lib/discourse_chat_integration/provider/rocketchat/rocketchat_provider.rb @@ -5,36 +5,45 @@ module DiscourseChatIntegration::Provider::RocketchatProvider PROVIDER_ENABLED_SETTING = :chat_integration_rocketchat_enabled - CHANNEL_PARAMETERS = [ - { key: "identifier", regex: '^[@#]\S*$', unique: true } - ] + CHANNEL_PARAMETERS = [{ key: "identifier", regex: '^[@#]\S*$', unique: true }] def self.rocketchat_message(post, channel) display_name = ::DiscourseChatIntegration::Helper.formatted_display_name(post.user) topic = post.topic - category = '' + category = "" if topic.category&.uncategorized? - category = "[#{I18n.t('uncategorized_category_name')}]" + category = "[#{I18n.t("uncategorized_category_name")}]" elsif topic.category - category = (topic.category.parent_category) ? "[#{topic.category.parent_category.name}/#{topic.category.name}]" : "[#{topic.category.name}]" + category = + ( + if (topic.category.parent_category) + "[#{topic.category.parent_category.name}/#{topic.category.name}]" + else + "[#{topic.category.name}]" + end + ) end - message = { - channel: channel, - attachments: [] - } + message = { channel: channel, attachments: [] } summary = { fallback: "#{topic.title} - #{display_name}", author_name: display_name, author_icon: post.user.small_avatar_url, color: topic.category ? "##{topic.category.color}" : nil, - text: post.excerpt(SiteSetting.chat_integration_rocketchat_excerpt_length, text_entities: true, strip_links: true, remap_emoji: true), + text: + post.excerpt( + SiteSetting.chat_integration_rocketchat_excerpt_length, + text_entities: true, + strip_links: true, + remap_emoji: true, + ), mrkdwn_in: ["text"], - title: "#{topic.title} #{category} #{topic.tags.present? ? topic.tags.map(&:name).join(', ') : ''}", - title_link: post.full_url + title: + "#{topic.title} #{category} #{topic.tags.present? ? topic.tags.map(&:name).join(", ") : ""}", + title_link: post.full_url, } message[:attachments].push(summary) @@ -46,25 +55,29 @@ module DiscourseChatIntegration::Provider::RocketchatProvider uri = URI(SiteSetting.chat_integration_rocketchat_webhook_url) http = FinalDestination::HTTP.new(uri.host, uri.port) - http.use_ssl = (uri.scheme == 'https') + http.use_ssl = (uri.scheme == "https") - req = Net::HTTP::Post.new(uri, 'Content-Type' => 'application/json') + req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json") req.body = message.to_json response = http.request(req) unless response.kind_of? Net::HTTPSuccess - if response.body.include?('invalid-channel') - error_key = 'chat_integration.provider.rocketchat.errors.invalid_channel' + if response.body.include?("invalid-channel") + error_key = "chat_integration.provider.rocketchat.errors.invalid_channel" else error_key = nil end - raise ::DiscourseChatIntegration::ProviderError.new info: { error_key: error_key, request: req.body, response_code: response.code, response_body: response.body } + raise ::DiscourseChatIntegration::ProviderError.new info: { + error_key: error_key, + request: req.body, + response_code: response.code, + response_body: response.body, + } end - end def self.trigger_notification(post, channel, rule) - channel_id = channel.data['identifier'] + channel_id = channel.data["identifier"] message = rocketchat_message(post, channel_id) self.send_via_webhook(message) diff --git a/lib/discourse_chat_integration/provider/slack/slack_command_controller.rb b/lib/discourse_chat_integration/provider/slack/slack_command_controller.rb index feabee0..31d972d 100644 --- a/lib/discourse_chat_integration/provider/slack/slack_command_controller.rb +++ b/lib/discourse_chat_integration/provider/slack/slack_command_controller.rb @@ -11,7 +11,7 @@ module DiscourseChatIntegration::Provider::SlackProvider :preload_json, :verify_authenticity_token, :redirect_to_login_if_required, - only: [:command, :interactive] + only: %i[command interactive] def command message = process_command(params) @@ -28,15 +28,14 @@ module DiscourseChatIntegration::Provider::SlackProvider private def process_command(params) - tokens = params[:text].split(" ") # channel name fix channel_id = case params[:channel_name] - when 'directmessage' + when "directmessage" "@#{params[:user_name]}" - when 'privategroup' + when "privategroup" params[:channel_id] else "##{params[:channel_name]}" @@ -44,17 +43,28 @@ module DiscourseChatIntegration::Provider::SlackProvider provider = DiscourseChatIntegration::Provider::SlackProvider::PROVIDER_NAME - channel = DiscourseChatIntegration::Channel.with_provider(provider) - .with_data_value('identifier', channel_id) - .first + channel = + DiscourseChatIntegration::Channel + .with_provider(provider) + .with_data_value("identifier", channel_id) + .first - channel ||= DiscourseChatIntegration::Channel.create!( - provider: provider, - data: { identifier: channel_id } - ) + channel ||= + DiscourseChatIntegration::Channel.create!( + provider: provider, + data: { + identifier: channel_id, + }, + ) - if tokens[0] == 'post' - process_post_request(channel, tokens, params[:channel_id], channel_id, params[:response_url]) + if tokens[0] == "post" + process_post_request( + channel, + tokens, + params[:channel_id], + channel_id, + params[:response_url], + ) else { text: ::DiscourseChatIntegration::Helper.process_command(channel, tokens) } end @@ -66,9 +76,10 @@ module DiscourseChatIntegration::Provider::SlackProvider end Scheduler::Defer.later "Processing slack transcript request" do - response = build_post_request_response(channel, tokens, slack_channel_id, channel_name, response_url) + response = + build_post_request_response(channel, tokens, slack_channel_id, channel_name, response_url) http = DiscourseChatIntegration::Provider::SlackProvider.slack_api_http - req = Net::HTTP::Post.new(URI(response_url), 'Content-Type' => 'application/json') + req = Net::HTTP::Post.new(URI(response_url), "Content-Type" => "application/json") req.body = response.to_json http.request(req) end @@ -81,15 +92,16 @@ module DiscourseChatIntegration::Provider::SlackProvider first_message_ts = nil requested_thread_ts = nil - thread_url_regex = /^https:\/\/\S+\.slack\.com\/archives\/\S+\/p[0-9]{16}\?thread_ts=([0-9]{10}.[0-9]{6})\S*$/ + thread_url_regex = + /^https:\/\/\S+\.slack\.com\/archives\/\S+\/p[0-9]{16}\?thread_ts=([0-9]{10}.[0-9]{6})\S*$/ slack_url_regex = /^https:\/\/\S+\.slack\.com\/archives\/\S+\/p([0-9]{16})\/?$/ if tokens.size > 2 && tokens[1] == "thread" && match = slack_url_regex.match(tokens[2]) - requested_thread_ts = match.captures[0].insert(10, '.') + requested_thread_ts = match.captures[0].insert(10, ".") elsif tokens.size > 1 && match = thread_url_regex.match(tokens[1]) requested_thread_ts = match.captures[0] elsif tokens.size > 1 && match = slack_url_regex.match(tokens[1]) - first_message_ts = match.captures[0].insert(10, '.') + first_message_ts = match.captures[0].insert(10, ".") elsif tokens.size > 1 begin requested_messages = Integer(tokens[1], 10) @@ -100,12 +112,21 @@ module DiscourseChatIntegration::Provider::SlackProvider error_key = "chat_integration.provider.slack.transcript.error" - return { text: I18n.t(error_key) } unless transcript = SlackTranscript.new(channel_name: channel_name, channel_id: slack_channel_id, requested_thread_ts: requested_thread_ts) + unless transcript = + SlackTranscript.new( + channel_name: channel_name, + channel_id: slack_channel_id, + requested_thread_ts: requested_thread_ts, + ) + return { text: I18n.t(error_key) } + end return { text: I18n.t("#{error_key}_users") } unless transcript.load_user_data return { text: I18n.t("#{error_key}_history") } unless transcript.load_chat_history if first_message_ts - return { text: I18n.t("#{error_key}_ts") } unless transcript.set_first_message_by_ts(first_message_ts) + unless transcript.set_first_message_by_ts(first_message_ts) + return { text: I18n.t("#{error_key}_ts") } + end elsif requested_messages transcript.set_first_message_by_index(-requested_messages) else @@ -123,22 +144,21 @@ module DiscourseChatIntegration::Provider::SlackProvider # Do nothing elsif json[:type] == "message_action" && json[:message][:thread_ts] # Context menu used on a threaded message - transcript = SlackTranscript.new( - channel_name: "##{json[:channel][:name]}", - channel_id: json[:channel][:id], - requested_thread_ts: json[:message][:thread_ts] - ) + transcript = + SlackTranscript.new( + channel_name: "##{json[:channel][:name]}", + channel_id: json[:channel][:id], + requested_thread_ts: json[:message][:thread_ts], + ) # Send a loading modal within 3 seconds: - req = Net::HTTP::Post.new( - "https://slack.com/api/views.open", - 'Content-Type' => 'application/json', - 'Authorization' => "Bearer #{SiteSetting.chat_integration_slack_access_token}" - ) - req.body = { - "trigger_id": json[:trigger_id], - "view": transcript.build_modal_ui - }.to_json + req = + Net::HTTP::Post.new( + "https://slack.com/api/views.open", + "Content-Type" => "application/json", + "Authorization" => "Bearer #{SiteSetting.chat_integration_slack_access_token}", + ) + req.body = { trigger_id: json[:trigger_id], view: transcript.build_modal_ui }.to_json response = http.request(req) view_id = JSON.parse(response.body).dig("view", "id") @@ -147,19 +167,17 @@ module DiscourseChatIntegration::Provider::SlackProvider error_view = generate_error_view("history") unless transcript.load_chat_history # Then update the modal with the transcript link: - req = Net::HTTP::Post.new( - "https://slack.com/api/views.update", - 'Content-Type' => 'application/json', - 'Authorization' => "Bearer #{SiteSetting.chat_integration_slack_access_token}" - ) - req.body = { - "view_id": view_id, - "view": error_view || transcript.build_modal_ui - }.to_json + req = + Net::HTTP::Post.new( + "https://slack.com/api/views.update", + "Content-Type" => "application/json", + "Authorization" => "Bearer #{SiteSetting.chat_integration_slack_access_token}", + ) + req.body = { view_id: view_id, view: error_view || transcript.build_modal_ui }.to_json response = http.request(req) else # Button clicked in one of our interactive messages - req = Net::HTTP::Post.new(URI(json[:response_url]), 'Content-Type' => 'application/json') + req = Net::HTTP::Post.new(URI(json[:response_url]), "Content-Type" => "application/json") req.body = build_interactive_response(json).to_json response = http.request(req) end @@ -177,26 +195,33 @@ module DiscourseChatIntegration::Provider::SlackProvider constant_val = json[:callback_id] changed_val = json[:actions][0][:selected_options][0][:value] - first_message = (action_name == 'first_message') ? changed_val : constant_val - last_message = (action_name == 'first_message') ? constant_val : changed_val + first_message = (action_name == "first_message") ? changed_val : constant_val + last_message = (action_name == "first_message") ? constant_val : changed_val end error_key = "chat_integration.provider.slack.transcript.error" - return { text: I18n.t(error_key) } unless transcript = SlackTranscript.new( - channel_name: "##{json[:channel][:name]}", - channel_id: json[:channel][:id], - requested_thread_ts: requested_thread - ) + unless transcript = + SlackTranscript.new( + channel_name: "##{json[:channel][:name]}", + channel_id: json[:channel][:id], + requested_thread_ts: requested_thread, + ) + return { text: I18n.t(error_key) } + end return { text: I18n.t("#{error_key}_users") } unless transcript.load_user_data return { text: I18n.t("#{error_key}_history") } unless transcript.load_chat_history if first_message - return { text: I18n.t("#{error_key}_ts") } unless transcript.set_first_message_by_ts(first_message) + unless transcript.set_first_message_by_ts(first_message) + return { text: I18n.t("#{error_key}_ts") } + end end if last_message - return { text: I18n.t("#{error_key}_ts") } unless transcript.set_last_message_by_ts(last_message) + unless transcript.set_last_message_by_ts(last_message) + return { text: I18n.t("#{error_key}_ts") } + end end transcript.build_slack_ui @@ -210,17 +235,11 @@ module DiscourseChatIntegration::Provider::SlackProvider type: "modal", title: { type: "plain_text", - text: I18n.t("chat_integration.provider.slack.transcript.modal_title") + text: I18n.t("chat_integration.provider.slack.transcript.modal_title"), }, blocks: [ - { - type: "section", - text: { - type: "mrkdwn", - text: ":warning: *#{I18n.t(error_key)}*" - } - } - ] + { type: "section", text: { type: "mrkdwn", text: ":warning: *#{I18n.t(error_key)}*" } }, + ], } end @@ -228,8 +247,7 @@ module DiscourseChatIntegration::Provider::SlackProvider params.require(:token) if SiteSetting.chat_integration_slack_incoming_webhook_token.blank? || - SiteSetting.chat_integration_slack_incoming_webhook_token != params[:token] - + SiteSetting.chat_integration_slack_incoming_webhook_token != params[:token] raise Discourse::InvalidAccess.new end end @@ -240,8 +258,7 @@ module DiscourseChatIntegration::Provider::SlackProvider json = JSON.parse(params[:payload], symbolize_names: true) if SiteSetting.chat_integration_slack_incoming_webhook_token.blank? || - SiteSetting.chat_integration_slack_incoming_webhook_token != json[:token] - + SiteSetting.chat_integration_slack_incoming_webhook_token != json[:token] raise Discourse::InvalidAccess.new end end @@ -256,5 +273,4 @@ module DiscourseChatIntegration::Provider::SlackProvider post "command" => "slack_command#command" post "interactive" => "slack_command#interactive" end - end diff --git a/lib/discourse_chat_integration/provider/slack/slack_enabled_setting_validator.rb b/lib/discourse_chat_integration/provider/slack/slack_enabled_setting_validator.rb index fd6ccc3..4daf442 100644 --- a/lib/discourse_chat_integration/provider/slack/slack_enabled_setting_validator.rb +++ b/lib/discourse_chat_integration/provider/slack/slack_enabled_setting_validator.rb @@ -6,13 +6,15 @@ class ChatIntegrationSlackEnabledSettingValidator end def valid_value?(val) - return true if val == ('f') || val == (false) - return false if SiteSetting.chat_integration_slack_outbound_webhook_url.blank? && SiteSetting.chat_integration_slack_access_token.blank? + return true if val == ("f") || val == (false) + if SiteSetting.chat_integration_slack_outbound_webhook_url.blank? && + SiteSetting.chat_integration_slack_access_token.blank? + return false + end true end def error_message - I18n.t('site_settings.errors.chat_integration_slack_api_configs_are_empty') + I18n.t("site_settings.errors.chat_integration_slack_api_configs_are_empty") end - end diff --git a/lib/discourse_chat_integration/provider/slack/slack_message.rb b/lib/discourse_chat_integration/provider/slack/slack_message.rb index b6a9271..e0be63e 100644 --- a/lib/discourse_chat_integration/provider/slack/slack_message.rb +++ b/lib/discourse_chat_integration/provider/slack/slack_message.rb @@ -12,7 +12,7 @@ module DiscourseChatIntegration::Provider::SlackProvider user["_transcript_username"] elsif @raw.key?("username") # This is for bot messages - @raw["username"].gsub(' ', '_') + @raw["username"].gsub(" ", "_") end end @@ -22,74 +22,69 @@ module DiscourseChatIntegration::Provider::SlackProvider def url channel_id = @transcript.channel_id - ts = @raw['ts'].gsub('.', '') + ts = @raw["ts"].gsub(".", "") "https://slack.com/archives/#{channel_id}/p#{ts}" end def text - text = @raw['text'].nil? ? "" : @raw['text'] + text = @raw["text"].nil? ? "" : @raw["text"] pre = {} # Extract code blocks and replace with placeholder - text = text.gsub(/```(.*?)```/m) do |match| - key = "pre:" + SecureRandom.alphanumeric(50) - pre[key] = HTMLEntities.new.decode $1 - "\n```\n#{key}\n```\n" - end + text = + text.gsub(/```(.*?)```/m) do |match| + key = "pre:" + SecureRandom.alphanumeric(50) + pre[key] = HTMLEntities.new.decode $1 + "\n```\n#{key}\n```\n" + end # # Extract inline code and replace with placeholder - text = text.gsub(/(?/) do |match| - group = $1 - parts = group.split('|') - link = parts[0].start_with?('@', '#', '!') ? nil : parts[0] - text = parts.length > 1 ? parts[1] : parts[0] + text = + text.gsub(/<(.*?)>/) do |match| + group = $1 + parts = group.split("|") + link = parts[0].start_with?("@", "#", "!") ? nil : parts[0] + text = parts.length > 1 ? parts[1] : parts[0] - if parts[0].start_with?('@') - user_id = parts[0][1..-1] - if user = @transcript.users[user_id] - user_name = user['_transcript_username'] - else - user_name = user_id + if parts[0].start_with?("@") + user_id = parts[0][1..-1] + if user = @transcript.users[user_id] + user_name = user["_transcript_username"] + else + user_name = user_id + end + next "@#{user_name}" end - next "@#{user_name}" - end - if link.nil? - text - elsif link == text - "<#{link}>" - else - "[#{text}](#{link})" + if link.nil? + text + elsif link == text + "<#{link}>" + else + "[#{text}](#{link})" + end end - end # Add an extra * to each side for bold - text = text.gsub(/\*.*?\*/) do |match| - "*#{match}*" - end + text = text.gsub(/\*.*?\*/) { |match| "*#{match}*" } # Add an extra ~ to each side for strikethrough - text = text.gsub(/~.*?~/) do |match| - "~#{match}~" - end + text = text.gsub(/~.*?~/) { |match| "~#{match}~" } # Replace emoji - with _ - text = text.gsub(/:[a-z0-9_-]+:/) do |match| - match.gsub("-") { "_" } - end + text = text.gsub(/:[a-z0-9_-]+:/) { |match| match.gsub("-") { "_" } } # Restore pre-formatted code block content - pre.each do |key, value| - text = text.gsub(key) { value } - end + pre.each { |key, value| text = text.gsub(key) { value } } text end @@ -97,9 +92,7 @@ module DiscourseChatIntegration::Provider::SlackProvider def attachments_string string = "" string += "\n" if !attachments.empty? - attachments.each do |attachment| - string += " - #{attachment}\n" - end + attachments.each { |attachment| string += " - #{attachment}\n" } string end @@ -108,7 +101,7 @@ module DiscourseChatIntegration::Provider::SlackProvider end def raw_text - raw_text = @raw['text'].nil? ? "" : @raw['text'] + raw_text = @raw["text"].nil? ? "" : @raw["text"] raw_text += attachments_string raw_text end @@ -116,7 +109,7 @@ module DiscourseChatIntegration::Provider::SlackProvider def attachments attachments = [] - return attachments unless @raw.key?('attachments') + return attachments unless @raw.key?("attachments") @raw["attachments"].each do |attachment| next unless attachment.key?("fallback") diff --git a/lib/discourse_chat_integration/provider/slack/slack_message_formatter.rb b/lib/discourse_chat_integration/provider/slack/slack_message_formatter.rb index 6d8f626..c3bdd4d 100644 --- a/lib/discourse_chat_integration/provider/slack/slack_message_formatter.rb +++ b/lib/discourse_chat_integration/provider/slack/slack_message_formatter.rb @@ -8,7 +8,7 @@ module DiscourseChatIntegration::Provider::SlackProvider @excerpt = +"" end - def self.format(html = '') + def self.format(html = "") me = self.new parser = Nokogiri::HTML::SAX::Parser.new(me) parser.parse(html) @@ -20,7 +20,7 @@ module DiscourseChatIntegration::Provider::SlackProvider when "a" attributes = Hash[*attributes.flatten] @in_a = true - @excerpt << "<#{absolute_url(attributes['href'])}|" + @excerpt << "<#{absolute_url(attributes["href"])}|" end end @@ -40,13 +40,18 @@ module DiscourseChatIntegration::Provider::SlackProvider private def absolute_url(url) - uri = URI(url) rescue nil + uri = + begin + URI(url) + rescue StandardError + nil + end return Discourse.current_hostname unless uri return uri.to_s if uri.scheme == "mailto" uri.host = Discourse.current_hostname if !uri.host - uri.scheme = (SiteSetting.force_https ? 'https' : 'http') if !uri.scheme + uri.scheme = (SiteSetting.force_https ? "https" : "http") if !uri.scheme uri.to_s end end diff --git a/lib/discourse_chat_integration/provider/slack/slack_provider.rb b/lib/discourse_chat_integration/provider/slack/slack_provider.rb index 8287066..4390deb 100644 --- a/lib/discourse_chat_integration/provider/slack/slack_provider.rb +++ b/lib/discourse_chat_integration/provider/slack/slack_provider.rb @@ -13,18 +13,19 @@ module DiscourseChatIntegration::Provider::SlackProvider PROVIDER_ENABLED_SETTING = :chat_integration_slack_enabled - CHANNEL_PARAMETERS = [ - { key: "identifier", regex: '^[@#]?\S*$', unique: true } - ] + CHANNEL_PARAMETERS = [{ key: "identifier", regex: '^[@#]?\S*$', unique: true }] - require_dependency 'topic' - ::Topic.register_custom_field_type(DiscourseChatIntegration::Provider::SlackProvider::THREAD_LEGACY, :string) + require_dependency "topic" + ::Topic.register_custom_field_type( + DiscourseChatIntegration::Provider::SlackProvider::THREAD_LEGACY, + :string, + ) def self.excerpt(post, max_length = SiteSetting.chat_integration_slack_excerpt_length) - doc = Nokogiri::HTML5.fragment(post.excerpt(max_length, - remap_emoji: true, - keep_onebox_source: true - )) + doc = + Nokogiri::HTML5.fragment( + post.excerpt(max_length, remap_emoji: true, keep_onebox_source: true), + ) SlackMessageFormatter.format(doc.to_html) end @@ -34,11 +35,18 @@ module DiscourseChatIntegration::Provider::SlackProvider topic = post.topic - category = '' + category = "" if topic.category&.uncategorized? - category = "[#{I18n.t('uncategorized_category_name')}]" + category = "[#{I18n.t("uncategorized_category_name")}]" elsif topic.category - category = (topic.category.parent_category) ? "[#{topic.category.parent_category.name}/#{topic.category.name}]" : "[#{topic.category.name}]" + category = + ( + if (topic.category.parent_category) + "[#{topic.category.parent_category.name}/#{topic.category.name}]" + else + "[#{topic.category.name}]" + end + ) end icon_url = @@ -55,12 +63,7 @@ module DiscourseChatIntegration::Provider::SlackProvider SiteSetting.title || "Discourse" end - message = { - channel: channel, - username: slack_username, - icon_url: icon_url, - attachments: [] - } + message = { channel: channel, username: slack_username, icon_url: icon_url, attachments: [] } if filter == "thread" && thread_ts = get_slack_thread_ts(topic, channel) message[:thread_ts] = thread_ts @@ -73,9 +76,10 @@ module DiscourseChatIntegration::Provider::SlackProvider color: topic.category ? "##{topic.category.color}" : nil, text: excerpt(post), mrkdwn_in: ["text"], - title: "#{topic.title} #{category} #{topic.tags.present? ? topic.tags.map(&:name).join(', ') : ''}", + title: + "#{topic.title} #{category} #{topic.tags.present? ? topic.tags.map(&:name).join(", ") : ""}", title_link: post.full_url, - thumb_url: post.full_url + thumb_url: post.full_url, } message[:attachments].push(summary) @@ -92,14 +96,14 @@ module DiscourseChatIntegration::Provider::SlackProvider # slack_thread_regex = // - req = Net::HTTP::Post.new(URI('https://slack.com/api/chat.postMessage')) + req = Net::HTTP::Post.new(URI("https://slack.com/api/chat.postMessage")) data = { token: SiteSetting.chat_integration_slack_access_token, username: message[:username], icon_url: message[:icon_url], - channel: message[:channel].gsub('#', ''), - attachments: message[:attachments].to_json + channel: message[:channel].gsub("#", ""), + attachments: message[:attachments].to_json, } if post @@ -116,18 +120,28 @@ module DiscourseChatIntegration::Provider::SlackProvider response = http.request(req) unless response.kind_of? Net::HTTPSuccess - raise ::DiscourseChatIntegration::ProviderError.new info: { request: uri, response_code: response.code, response_body: response.body } + raise ::DiscourseChatIntegration::ProviderError.new info: { + request: uri, + response_code: response.code, + response_body: response.body, + } end json = JSON.parse(response.body) unless json["ok"] == true - if json.key?("error") && (json["error"] == ('channel_not_found') || json["error"] == ('is_archived')) - error_key = 'chat_integration.provider.slack.errors.channel_not_found' + if json.key?("error") && + (json["error"] == ("channel_not_found") || json["error"] == ("is_archived")) + error_key = "chat_integration.provider.slack.errors.channel_not_found" else error_key = nil end - raise ::DiscourseChatIntegration::ProviderError.new info: { error_key: error_key, request: uri, response_code: response.code, response_body: response.body } + raise ::DiscourseChatIntegration::ProviderError.new info: { + error_key: error_key, + request: uri, + response_code: response.code, + response_body: response.body, + } end ts = json["ts"] @@ -139,25 +153,33 @@ module DiscourseChatIntegration::Provider::SlackProvider def self.send_via_webhook(message) http = FinalDestination::HTTP.new("hooks.slack.com", 443) http.use_ssl = true - req = Net::HTTP::Post.new(URI(SiteSetting.chat_integration_slack_outbound_webhook_url), 'Content-Type' => 'application/json') + req = + Net::HTTP::Post.new( + URI(SiteSetting.chat_integration_slack_outbound_webhook_url), + "Content-Type" => "application/json", + ) req.body = message.to_json response = http.request(req) unless response.kind_of? Net::HTTPSuccess - if response.code.to_s == '403' - error_key = 'chat_integration.provider.slack.errors.action_prohibited' - elsif response.body == ('channel_not_found') || response.body == ('channel_is_archived') - error_key = 'chat_integration.provider.slack.errors.channel_not_found' + if response.code.to_s == "403" + error_key = "chat_integration.provider.slack.errors.action_prohibited" + elsif response.body == ("channel_not_found") || response.body == ("channel_is_archived") + error_key = "chat_integration.provider.slack.errors.channel_not_found" else error_key = nil end - raise ::DiscourseChatIntegration::ProviderError.new info: { error_key: error_key, request: req.body, response_code: response.code, response_body: response.body } + raise ::DiscourseChatIntegration::ProviderError.new info: { + error_key: error_key, + request: req.body, + response_code: response.code, + response_body: response.body, + } end - end def self.trigger_notification(post, channel, rule) - channel_id = channel.data['identifier'] + channel_id = channel.data["identifier"] filter = rule.nil? ? "" : rule.filter message = slack_message(post, channel_id, filter) @@ -166,7 +188,6 @@ module DiscourseChatIntegration::Provider::SlackProvider else self.send_via_api(post, channel_id, message) end - end def self.slack_api_http @@ -182,13 +203,16 @@ module DiscourseChatIntegration::Provider::SlackProvider end def self.set_slack_thread_ts(topic, channel, value) - TopicCustomField.upsert({ + TopicCustomField.upsert( + { topic_id: topic.id, name: "#{THREAD_CUSTOM_FIELD_PREFIX}#{channel}", value: value, created_at: Time.zone.now, - updated_at: Time.zone.now - }, unique_by: [:topic_id, :name]) + updated_at: Time.zone.now, + }, + unique_by: %i[topic_id name], + ) end end diff --git a/lib/discourse_chat_integration/provider/slack/slack_transcript.rb b/lib/discourse_chat_integration/provider/slack/slack_transcript.rb index 8ad29dd..8b648b5 100644 --- a/lib/discourse_chat_integration/provider/slack/slack_transcript.rb +++ b/lib/discourse_chat_integration/provider/slack/slack_transcript.rb @@ -2,7 +2,8 @@ module DiscourseChatIntegration::Provider::SlackProvider class SlackTranscript - class UserFetchError < RuntimeError; end + class UserFetchError < RuntimeError + end attr_reader :users, :channel_id, :messages @@ -42,17 +43,14 @@ module DiscourseChatIntegration::Provider::SlackProvider # Work through the messages in order. If a gap is found, this could be the first message new_first_message_index = nil - previous_message_ts = @messages[-skip_messages].ts.split('.').first.to_i + previous_message_ts = @messages[-skip_messages].ts.split(".").first.to_i possible_first_messages.each_with_index do |message, index| - # Calculate the time since the last message - this_ts = message.ts.split('.').first.to_i + this_ts = message.ts.split(".").first.to_i time_since_previous_message = this_ts - previous_message_ts # If greater than 3 minutes, this could be the first message - if time_since_previous_message > 3.minutes - new_first_message_index = index - end + new_first_message_index = index if time_since_previous_message > 3.minutes previous_message_ts = this_ts end @@ -84,11 +82,11 @@ module DiscourseChatIntegration::Provider::SlackProvider def build_transcript post_content = +"" post_content << "[quote]\n" if SiteSetting.chat_integration_slack_transcript_quote - post_content << "[**#{I18n.t('chat_integration.provider.slack.transcript.view_on_slack', name: @channel_name)}**](#{first_message.url})\n" + post_content << "[**#{I18n.t("chat_integration.provider.slack.transcript.view_on_slack", name: @channel_name)}**](#{first_message.url})\n" all_avatars = {} - last_username = '' + last_username = "" transcript_messages = @messages[@first_message_index..@last_message_index] @@ -108,9 +106,7 @@ module DiscourseChatIntegration::Provider::SlackProvider post_content << m.text - m.attachments.each do |attachment| - post_content << "\n> #{attachment}\n" - end + m.attachments.each { |attachment| post_content << "\n> #{attachment}\n" } post_content << "\n" end @@ -118,9 +114,7 @@ module DiscourseChatIntegration::Provider::SlackProvider post_content << "[/quote]" if SiteSetting.chat_integration_slack_transcript_quote post_content << "\n\n" - all_avatars.each do |username, url| - post_content << "[#{username}]: #{url}\n" - end + all_avatars.each { |username, url| post_content << "[#{username}]: #{url}\n" } if not @requested_thread_ts.nil? post_content << "" @@ -134,17 +128,17 @@ module DiscourseChatIntegration::Provider::SlackProvider type: "modal", title: { type: "plain_text", - text: I18n.t("chat_integration.provider.slack.transcript.modal_title") + text: I18n.t("chat_integration.provider.slack.transcript.modal_title"), }, blocks: [ { - "type": "section", - "text": { - "type": "mrkdwn", - "text": I18n.t("chat_integration.provider.slack.transcript.modal_description") - } - } - ] + type: "section", + text: { + type: "mrkdwn", + text: I18n.t("chat_integration.provider.slack.transcript.modal_description"), + }, + }, + ], } if @messages @@ -153,30 +147,31 @@ module DiscourseChatIntegration::Provider::SlackProvider link = "#{Discourse.base_url}/chat-transcript/#{secret}" data[:blocks] << { - "type": "section", - "text": { - "type": "mrkdwn", - "text": ":writing_hand: *#{I18n.t("chat_integration.provider.slack.transcript.transcript_ready")}*" + type: "section", + text: { + type: "mrkdwn", + text: + ":writing_hand: *#{I18n.t("chat_integration.provider.slack.transcript.transcript_ready")}*", }, - "accessory": { - "type": "button", - "text": { - "type": "plain_text", - "text": I18n.t("chat_integration.provider.slack.transcript.continue_on_discourse"), - "emoji": true + accessory: { + type: "button", + text: { + type: "plain_text", + text: I18n.t("chat_integration.provider.slack.transcript.continue_on_discourse"), + emoji: true, }, - "style": "primary", - "url": link, - "action_id": "null_action" - } + style: "primary", + url: link, + action_id: "null_action", + }, } else data[:blocks] << { - "type": "section", - "text": { - "type": "mrkdwn", - "text": ":writing_hand: #{I18n.t("chat_integration.provider.slack.transcript.loading")}" - } + type: "section", + text: { + type: "mrkdwn", + text: ":writing_hand: #{I18n.t("chat_integration.provider.slack.transcript.loading")}", + }, } end @@ -188,76 +183,81 @@ module DiscourseChatIntegration::Provider::SlackProvider secret = DiscourseChatIntegration::Helper.save_transcript(post_content) link = "#{Discourse.base_url}/chat-transcript/#{secret}" - return { text: "<#{link}|#{I18n.t("chat_integration.provider.slack.transcript.post_to_discourse")}>" } if @requested_thread_ts + if @requested_thread_ts + return( + { + text: + "<#{link}|#{I18n.t("chat_integration.provider.slack.transcript.post_to_discourse")}>", + } + ) + end { text: "<#{link}|#{I18n.t("chat_integration.provider.slack.transcript.post_to_discourse")}>", attachments: [ { - pretext: I18n.t( - "chat_integration.provider.slack.transcript.first_message_pretext", - n: @messages.length - first_message_number - ), + pretext: + I18n.t( + "chat_integration.provider.slack.transcript.first_message_pretext", + n: @messages.length - first_message_number, + ), fallback: "#{first_message.username} - #{first_message.raw_text}", color: "#007AB8", author_name: first_message.username, author_icon: first_message.avatar, text: first_message.raw_text, - footer: I18n.t( - "chat_integration.provider.slack.transcript.posted_in", - name: @channel_name - ), + footer: + I18n.t("chat_integration.provider.slack.transcript.posted_in", name: @channel_name), ts: first_message.ts, callback_id: last_message.ts, actions: [ { name: "first_message", - text: I18n.t( - "chat_integration.provider.slack.transcript.change_first_message" - ), + text: I18n.t("chat_integration.provider.slack.transcript.change_first_message"), type: "select", - options: first_message_options = @messages[ [(first_message_number - 20), 0].max .. last_message_number] - .map { |m| { text: "#{m.username}: #{m.processed_text_with_attachments}", value: m.ts } } - } + options: + first_message_options = + @messages[[(first_message_number - 20), 0].max..last_message_number].map do |m| + { text: "#{m.username}: #{m.processed_text_with_attachments}", value: m.ts } + end, + }, ], }, { - pretext: I18n.t( - "chat_integration.provider.slack.transcript.last_message_pretext", - n: @messages.length - last_message_number - ), + pretext: + I18n.t( + "chat_integration.provider.slack.transcript.last_message_pretext", + n: @messages.length - last_message_number, + ), fallback: "#{last_message.username} - #{last_message.raw_text}", color: "#007AB8", author_name: last_message.username, author_icon: last_message.avatar, text: last_message.raw_text, - footer: I18n.t( - "chat_integration.provider.slack.transcript.posted_in", - name: @channel_name - ), + footer: + I18n.t("chat_integration.provider.slack.transcript.posted_in", name: @channel_name), ts: last_message.ts, callback_id: first_message.ts, actions: [ { name: "last_message", - text: I18n.t( - "chat_integration.provider.slack.transcript.change_last_message" - ), + text: I18n.t("chat_integration.provider.slack.transcript.change_last_message"), type: "select", - options: @messages[first_message_number..(last_message_number + 20)] - .map { |m| { text: "#{m.username}: #{m.processed_text_with_attachments}", value: m.ts } } - } + options: + @messages[first_message_number..(last_message_number + 20)].map do |m| + { text: "#{m.username}: #{m.processed_text_with_attachments}", value: m.ts } + end, + }, ], - } - ] + }, + ], } end def load_user_data - key = "slack_user_info_#{Digest::SHA1.hexdigest(SiteSetting.chat_integration_slack_access_token)}" - @users = Discourse.cache.fetch(key, expires_in: 10.minutes) do - fetch_user_data - end + key = + "slack_user_info_#{Digest::SHA1.hexdigest(SiteSetting.chat_integration_slack_access_token)}" + @users = Discourse.cache.fetch(key, expires_in: 10.minutes) { fetch_user_data } true rescue UserFetchError false @@ -267,26 +267,30 @@ module DiscourseChatIntegration::Provider::SlackProvider http = ::DiscourseChatIntegration::Provider::SlackProvider.slack_api_http cursor = nil - req = Net::HTTP::Post.new(URI('https://slack.com/api/users.list')) + req = Net::HTTP::Post.new(URI("https://slack.com/api/users.list")) users = {} loop do break if cursor == "" - req.set_form_data(token: SiteSetting.chat_integration_slack_access_token, limit: 200, cursor: cursor) + req.set_form_data( + token: SiteSetting.chat_integration_slack_access_token, + limit: 200, + cursor: cursor, + ) response = http.request(req) raise UserFetchError.new unless response.kind_of? Net::HTTPSuccess json = JSON.parse(response.body) - raise UserFetchError.new unless json['ok'] - cursor = json['response_metadata']['next_cursor'] - json['members'].each do |user| + raise UserFetchError.new unless json["ok"] + cursor = json["response_metadata"]["next_cursor"] + json["members"].each do |user| # Slack uses display_name and falls back to real_name if it is not set - if user['profile']['display_name'].blank? - user['_transcript_username'] = user['profile']['real_name'] + if user["profile"]["display_name"].blank? + user["_transcript_username"] = user["profile"]["real_name"] else - user['_transcript_username'] = user['profile']['display_name'] + user["_transcript_username"] = user["profile"]["display_name"] end - user['_transcript_username'] = user['_transcript_username'].gsub(' ', '_') - users[user['id']] = user + user["_transcript_username"] = user["_transcript_username"].gsub(" ", "_") + users[user["id"]] = user end end users @@ -302,7 +306,7 @@ module DiscourseChatIntegration::Provider::SlackProvider data = { token: SiteSetting.chat_integration_slack_access_token, channel: @channel_id, - limit: count + limit: count, } data[:ts] = @requested_thread_ts if @requested_thread_ts @@ -311,9 +315,9 @@ module DiscourseChatIntegration::Provider::SlackProvider response = http.request(req) return false unless response.kind_of? Net::HTTPSuccess json = JSON.parse(response.body) - return false unless json['ok'] + return false unless json["ok"] - raw_messages = json['messages'] + raw_messages = json["messages"] raw_messages = raw_messages.reverse unless @requested_thread_ts # Build some message objects @@ -323,13 +327,13 @@ module DiscourseChatIntegration::Provider::SlackProvider next unless message["type"] == "message" # Don't load responses to threads unless specifically requested (if ts==thread_ts then it's the thread parent) - next if !@requested_thread_ts && message["thread_ts"] && message["thread_ts"] != message["ts"] + if !@requested_thread_ts && message["thread_ts"] && message["thread_ts"] != message["ts"] + next + end this_message = SlackMessage.new(message, self) @messages << this_message end - end end - end diff --git a/lib/discourse_chat_integration/provider/teams/teams_provider.rb b/lib/discourse_chat_integration/provider/teams/teams_provider.rb index 1c2aeca..5709376 100644 --- a/lib/discourse_chat_integration/provider/teams/teams_provider.rb +++ b/lib/discourse_chat_integration/provider/teams/teams_provider.rb @@ -5,29 +5,33 @@ module DiscourseChatIntegration::Provider::TeamsProvider PROVIDER_ENABLED_SETTING = :chat_integration_teams_enabled CHANNEL_PARAMETERS = [ { key: "name", regex: '^\S+$', unique: true }, - { key: "webhook_url", regex: '^https:\/\/\S+$', unique: true, hidden: true } + { key: "webhook_url", regex: '^https:\/\/\S+$', unique: true, hidden: true }, ] def self.trigger_notification(post, channel, rule) message = get_message(post) - uri = URI(channel.data['webhook_url']) + uri = URI(channel.data["webhook_url"]) http = FinalDestination::HTTP.new(uri.host, uri.port) - http.use_ssl = (uri.scheme == 'https') + http.use_ssl = (uri.scheme == "https") - req = Net::HTTP::Post.new(uri, 'Content-Type' => 'application/json') + req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json") req.body = message.to_json response = http.request(req) unless response.kind_of? Net::HTTPSuccess - if response.body.include?('Invalid webhook URL') - error_key = 'chat_integration.provider.teams.errors.invalid_channel' + if response.body.include?("Invalid webhook URL") + error_key = "chat_integration.provider.teams.errors.invalid_channel" else error_key = nil end - raise ::DiscourseChatIntegration::ProviderError.new info: { error_key: error_key, request: req.body, response_code: response.code, response_body: response.body } + raise ::DiscourseChatIntegration::ProviderError.new info: { + error_key: error_key, + request: req.body, + response_code: response.code, + response_body: response.body, + } end - end def self.get_message(post) @@ -41,29 +45,41 @@ module DiscourseChatIntegration::Provider::TeamsProvider topic = post.topic - category = '' + category = "" if topic.category&.uncategorized? - category = "[#{I18n.t('uncategorized_category_name')}]" + category = "[#{I18n.t("uncategorized_category_name")}]" elsif topic.category - category = (topic.category.parent_category) ? "[#{topic.category.parent_category.name}/#{topic.category.name}]" : "[#{topic.category.name}]" + category = + ( + if (topic.category.parent_category) + "[#{topic.category.parent_category.name}/#{topic.category.name}]" + else + "[#{topic.category.name}]" + end + ) end message = { "@type": "MessageCard", - "summary": topic.title, - "sections": [{ - "activityTitle": "[#{topic.title} #{category} #{topic.tags.present? ? topic.tags.map(&:name).join(', ') : ''}](#{post.full_url})", - "activitySubtitle": post.excerpt(SiteSetting.chat_integration_teams_excerpt_length, text_entities: true, strip_links: true, remap_emoji: true), - "activityImage": post.user.small_avatar_url, - "facts": [{ - "name": full_name, - "value": display_name - }], - "markdown": true - }], + summary: topic.title, + sections: [ + { + activityTitle: + "[#{topic.title} #{category} #{topic.tags.present? ? topic.tags.map(&:name).join(", ") : ""}](#{post.full_url})", + activitySubtitle: + post.excerpt( + SiteSetting.chat_integration_teams_excerpt_length, + text_entities: true, + strip_links: true, + remap_emoji: true, + ), + activityImage: post.user.small_avatar_url, + facts: [{ name: full_name, value: display_name }], + markdown: true, + }, + ], } message end - end diff --git a/lib/discourse_chat_integration/provider/telegram/telegram_command_controller.rb b/lib/discourse_chat_integration/provider/telegram/telegram_command_controller.rb index fe01d9a..1b8ac8e 100644 --- a/lib/discourse_chat_integration/provider/telegram/telegram_command_controller.rb +++ b/lib/discourse_chat_integration/provider/telegram/telegram_command_controller.rb @@ -13,12 +13,11 @@ module DiscourseChatIntegration::Provider::TelegramProvider only: :command def command - # If it's a new message (telegram also sends hooks for other reasons that we don't care about) - if params.key?('message') - chat_id = params['message']['chat']['id'] + if params.key?("message") + chat_id = params["message"]["chat"]["id"] - message_text = process_command(params['message']) + message_text = process_command(params["message"]) if message_text.present? message = { @@ -30,15 +29,15 @@ module DiscourseChatIntegration::Provider::TelegramProvider DiscourseChatIntegration::Provider::TelegramProvider.sendMessage(message) end + elsif params.dig("channel_post", "text")&.include?("/getchatid") + chat_id = params["channel_post"]["chat"]["id"] - elsif params.dig('channel_post', 'text')&.include?('/getchatid') - chat_id = params['channel_post']['chat']['id'] - - message_text = I18n.t( - "chat_integration.provider.telegram.unknown_chat", - site_title: CGI::escapeHTML(SiteSetting.title), - chat_id: chat_id, - ) + message_text = + I18n.t( + "chat_integration.provider.telegram.unknown_chat", + site_title: CGI.escapeHTML(SiteSetting.title), + chat_id: chat_id, + ) message = { chat_id: chat_id, @@ -51,43 +50,49 @@ module DiscourseChatIntegration::Provider::TelegramProvider end # Always give telegram a success message, otherwise we'll stop receiving webhooks - data = { - success: true - } + data = { success: true } render json: data end def process_command(message) - return unless message['text'] # No command to be processed + return unless message["text"] # No command to be processed - chat_id = params['message']['chat']['id'] + chat_id = params["message"]["chat"]["id"] provider = DiscourseChatIntegration::Provider::TelegramProvider::PROVIDER_NAME - channel = DiscourseChatIntegration::Channel.with_provider(provider).with_data_value('chat_id', chat_id).first + channel = + DiscourseChatIntegration::Channel + .with_provider(provider) + .with_data_value("chat_id", chat_id) + .first - text_key = if channel.nil? - "unknown_chat" - elsif !SiteSetting.chat_integration_telegram_enable_slash_commands || !message['text'].start_with?('/') - "silent" - else - "" - end + text_key = + if channel.nil? + "unknown_chat" + elsif !SiteSetting.chat_integration_telegram_enable_slash_commands || + !message["text"].start_with?("/") + "silent" + else + "" + end return "" if text_key == "silent" if text_key.present? - return I18n.t( - "chat_integration.provider.telegram.#{text_key}", - site_title: CGI::escapeHTML(SiteSetting.title), - chat_id: chat_id, + return( + I18n.t( + "chat_integration.provider.telegram.#{text_key}", + site_title: CGI.escapeHTML(SiteSetting.title), + chat_id: chat_id, + ) ) end - tokens = message['text'].split(" ") + tokens = message["text"].split(" ") - tokens[0][0] = '' # Remove the slash from the first token - tokens[0] = tokens[0].split('@')[0] # Remove the bot name from the command (necessary for group chats) + tokens[0][0] = "" # Remove the slash from the first token + tokens[0] = tokens[0].split("@")[0] # Remove the bot name from the command (necessary for group chats) ::DiscourseChatIntegration::Helper.process_command(channel, tokens) end @@ -96,8 +101,7 @@ module DiscourseChatIntegration::Provider::TelegramProvider params.require(:token) if SiteSetting.chat_integration_telegram_secret.blank? || - SiteSetting.chat_integration_telegram_secret != params[:token] - + SiteSetting.chat_integration_telegram_secret != params[:token] raise Discourse::InvalidAccess.new end end @@ -108,7 +112,5 @@ module DiscourseChatIntegration::Provider::TelegramProvider isolate_namespace DiscourseChatIntegration::Provider::TelegramProvider end - TelegramEngine.routes.draw do - post "command/:token" => "telegram_command#command" - end + TelegramEngine.routes.draw { post "command/:token" => "telegram_command#command" } end diff --git a/lib/discourse_chat_integration/provider/telegram/telegram_initializer.rb b/lib/discourse_chat_integration/provider/telegram/telegram_initializer.rb index f28500c..fc1e70c 100644 --- a/lib/discourse_chat_integration/provider/telegram/telegram_initializer.rb +++ b/lib/discourse_chat_integration/provider/telegram/telegram_initializer.rb @@ -2,7 +2,7 @@ DiscourseEvent.on(:site_setting_changed) do |setting_name, old_value, new_value| isEnabledSetting = setting_name == :chat_integration_telegram_enabled - isAccessToken = setting_name == :chat_integration_telegram_access_token + isAccessToken = setting_name == :chat_integration_telegram_access_token if (isEnabledSetting || isAccessToken) enabled = isEnabledSetting ? new_value == true : SiteSetting.chat_integration_telegram_enabled diff --git a/lib/discourse_chat_integration/provider/telegram/telegram_provider.rb b/lib/discourse_chat_integration/provider/telegram/telegram_provider.rb index a5d6fe8..c0d909c 100644 --- a/lib/discourse_chat_integration/provider/telegram/telegram_provider.rb +++ b/lib/discourse_chat_integration/provider/telegram/telegram_provider.rb @@ -6,30 +6,30 @@ module DiscourseChatIntegration PROVIDER_NAME = "telegram".freeze PROVIDER_ENABLED_SETTING = :chat_integration_telegram_enabled CHANNEL_PARAMETERS = [ - { key: "name", regex: '^\S+' }, - { key: "chat_id", regex: '^(-?[0-9]+|@\S+)$', unique: true } - ] + { key: "name", regex: '^\S+' }, + { key: "chat_id", regex: '^(-?[0-9]+|@\S+)$', unique: true }, + ] def self.setup_webhook newSecret = SecureRandom.hex SiteSetting.chat_integration_telegram_secret = newSecret - message = { - url: Discourse.base_url + '/chat-integration/telegram/command/' + newSecret, - } + message = { url: Discourse.base_url + "/chat-integration/telegram/command/" + newSecret } - response = self.do_api_request('setWebhook', message) + response = self.do_api_request("setWebhook", message) - if response['ok'] != true + if response["ok"] != true # If setting up webhook failed, disable provider SiteSetting.chat_integration_telegram_enabled = false - Rails.logger.error("Failed to setup telegram webhook. Message data= " + message.to_json + " response=" + response.to_json) + Rails.logger.error( + "Failed to setup telegram webhook. Message data= " + message.to_json + " response=" + + response.to_json, + ) end - end def self.sendMessage(message) - self.do_api_request('sendMessage', message) + self.do_api_request("sendMessage", message) end def self.do_api_request(methodName, message) @@ -40,7 +40,7 @@ module DiscourseChatIntegration uri = URI("https://api.telegram.org/bot#{access_token}/#{methodName}") - req = Net::HTTP::Post.new(uri, 'Content-Type' => 'application/json') + req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json") req.body = message.to_json response = http.request(req) @@ -54,28 +54,38 @@ module DiscourseChatIntegration topic = post.topic - category = '' + category = "" if topic.category - category = (topic.category.parent_category) ? "[#{topic.category.parent_category.name}/#{topic.category.name}]" : "[#{topic.category.name}]" + category = + ( + if (topic.category.parent_category) + "[#{topic.category.parent_category.name}/#{topic.category.name}]" + else + "[#{topic.category.name}]" + end + ) end - tags = '' - if topic.tags.present? - tags = topic.tags.map(&:name).join(', ') - end + tags = "" + tags = topic.tags.map(&:name).join(", ") if topic.tags.present? I18n.t( - "chat_integration.provider.telegram.message", - user: display_name, - post_url: post.full_url, - title: CGI::escapeHTML(topic.title), - post_excerpt: post.excerpt(SiteSetting.chat_integration_telegram_excerpt_length, text_entities: true, strip_links: true, remap_emoji: true), - ) - + "chat_integration.provider.telegram.message", + user: display_name, + post_url: post.full_url, + title: CGI.escapeHTML(topic.title), + post_excerpt: + post.excerpt( + SiteSetting.chat_integration_telegram_excerpt_length, + text_entities: true, + strip_links: true, + remap_emoji: true, + ), + ) end def self.trigger_notification(post, channel, rule) - chat_id = channel.data['chat_id'] + chat_id = channel.data["chat_id"] message = { chat_id: chat_id, @@ -86,18 +96,20 @@ module DiscourseChatIntegration response = sendMessage(message) - if response['ok'] != true + if response["ok"] != true error_key = nil - if response['description'].include? 'chat not found' - error_key = 'chat_integration.provider.telegram.errors.channel_not_found' - elsif response['description'].include? 'Forbidden' - error_key = 'chat_integration.provider.telegram.errors.forbidden' + if response["description"].include? "chat not found" + error_key = "chat_integration.provider.telegram.errors.channel_not_found" + elsif response["description"].include? "Forbidden" + error_key = "chat_integration.provider.telegram.errors.forbidden" end - raise ::DiscourseChatIntegration::ProviderError.new info: { error_key: error_key, message: message, response_body: response } + raise ::DiscourseChatIntegration::ProviderError.new info: { + error_key: error_key, + message: message, + response_body: response, + } end - end - end end end diff --git a/lib/discourse_chat_integration/provider/webex/webex_provider.rb b/lib/discourse_chat_integration/provider/webex/webex_provider.rb index f61fa1d..f64d35a 100644 --- a/lib/discourse_chat_integration/provider/webex/webex_provider.rb +++ b/lib/discourse_chat_integration/provider/webex/webex_provider.rb @@ -5,35 +5,38 @@ module DiscourseChatIntegration::Provider::WebexProvider PROVIDER_ENABLED_SETTING = :chat_integration_webex_enabled CHANNEL_PARAMETERS = [ { key: "name", regex: '^\S+$', unique: true }, - { key: "webhook_url", + { + key: "webhook_url", regex: '^https:\/\/webexapis\.com\/v1\/webhooks\/incoming\/[A-Za-z0-9\-@\/]+\S+$', unique: true, - hidden: true } + hidden: true, + }, ] def self.trigger_notification(post, channel, rule) message = get_message(post) - uri = URI(channel.data['webhook_url']) + uri = URI(channel.data["webhook_url"]) http = FinalDestination::HTTP.new(uri.host, uri.port) - http.use_ssl = (uri.scheme == 'https') + http.use_ssl = (uri.scheme == "https") - req = Net::HTTP::Post.new(uri, 'Content-Type' => 'application/json') + req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json") req.body = message.to_json response = http.request(req) unless response.kind_of? Net::HTTPSuccess - if response.body.include?('Invalid webhook URL') - error_key = 'chat_integration.provider.webex.errors.invalid_channel' + if response.body.include?("Invalid webhook URL") + error_key = "chat_integration.provider.webex.errors.invalid_channel" else error_key = nil end - raise ::DiscourseChatIntegration::ProviderError.new info: { error_key: error_key, - request: req.body, - response_code: response.code, - response_body: response.body } + raise ::DiscourseChatIntegration::ProviderError.new info: { + error_key: error_key, + request: req.body, + response_code: response.code, + response_body: response.body, + } end - end def self.get_message(post) @@ -41,24 +44,31 @@ module DiscourseChatIntegration::Provider::WebexProvider topic = post.topic - category = '' + category = "" if topic.category&.uncategorized? - category = "[#{I18n.t('uncategorized_category_name')}]" + category = "[#{I18n.t("uncategorized_category_name")}]" elsif topic.category - category = (topic.category.parent_category) ? - "[#{topic.category.parent_category.name}/#{topic.category.name}]" : "[#{topic.category.name}]" + category = + ( + if (topic.category.parent_category) + "[#{topic.category.parent_category.name}/#{topic.category.name}]" + else + "[#{topic.category.name}]" + end + ) end markdown = "**#{topic.title}**: #{category}" - markdown += " #{topic.tags.map(&:name).join(', ')} " if topic.tags.present? + markdown += " #{topic.tags.map(&:name).join(", ")} " if topic.tags.present? markdown += "(#{post.full_url}) from #{display_name}:\n" - markdown += post.excerpt(SiteSetting.chat_integration_webex_excerpt_length, - text_entities: true, - strip_links: true, - remap_emoji: true - ) + markdown += + post.excerpt( + SiteSetting.chat_integration_webex_excerpt_length, + text_entities: true, + strip_links: true, + remap_emoji: true, + ) - { "markdown": markdown } + { markdown: markdown } end - end diff --git a/lib/discourse_chat_integration/provider/zulip/zulip_provider.rb b/lib/discourse_chat_integration/provider/zulip/zulip_provider.rb index cd9929b..36e400e 100644 --- a/lib/discourse_chat_integration/provider/zulip/zulip_provider.rb +++ b/lib/discourse_chat_integration/provider/zulip/zulip_provider.rb @@ -6,18 +6,21 @@ module DiscourseChatIntegration PROVIDER_NAME = "zulip".freeze PROVIDER_ENABLED_SETTING = :chat_integration_zulip_enabled CHANNEL_PARAMETERS = [ - { key: "stream", unique: true, regex: '^\S+' }, - { key: "subject", unique: true, regex: '^\S+' }, - ] + { key: "stream", unique: true, regex: '^\S+' }, + { key: "subject", unique: true, regex: '^\S+' }, + ] def self.send_message(message) uri = URI("#{SiteSetting.chat_integration_zulip_server}/api/v1/messages") http = FinalDestination::HTTP.new(uri.host, uri.port) - http.use_ssl = (uri.scheme == 'https') + http.use_ssl = (uri.scheme == "https") req = Net::HTTP::Post.new(uri) - req.basic_auth(SiteSetting.chat_integration_zulip_bot_email_address, SiteSetting.chat_integration_zulip_bot_api_key) + req.basic_auth( + SiteSetting.chat_integration_zulip_bot_email_address, + SiteSetting.chat_integration_zulip_bot_api_key, + ) req.set_form_data(message) response = http.request(req) @@ -28,23 +31,27 @@ module DiscourseChatIntegration def self.generate_zulip_message(post, stream, subject) display_name = ::DiscourseChatIntegration::Helper.formatted_display_name(post.user) - message = I18n.t('chat_integration.provider.zulip.message', user: display_name, - post_url: post.full_url, - title: post.topic.title, - excerpt: post.excerpt(SiteSetting.chat_integration_zulip_excerpt_length, text_entities: true, strip_links: true, remap_emoji: true)) + message = + I18n.t( + "chat_integration.provider.zulip.message", + user: display_name, + post_url: post.full_url, + title: post.topic.title, + excerpt: + post.excerpt( + SiteSetting.chat_integration_zulip_excerpt_length, + text_entities: true, + strip_links: true, + remap_emoji: true, + ), + ) - data = { - type: 'stream', - to: stream, - subject: subject, - content: message - } + data = { type: "stream", to: stream, subject: subject, content: message } end def self.trigger_notification(post, channel, rule) - - stream = channel.data['stream'] - subject = channel.data['subject'] + stream = channel.data["stream"] + subject = channel.data["subject"] message = self.generate_zulip_message(post, stream, subject) @@ -52,12 +59,18 @@ module DiscourseChatIntegration if !response.kind_of?(Net::HTTPSuccess) error_key = nil - error_key = 'chat_integration.provider.zulip.errors.does_not_exist' if response.body.include?('does not exist') - raise ::DiscourseChatIntegration::ProviderError.new info: { error_key: error_key, message: message, response_code: response.code, response_body: response.body } + error_key = + "chat_integration.provider.zulip.errors.does_not_exist" if response.body.include?( + "does not exist", + ) + raise ::DiscourseChatIntegration::ProviderError.new info: { + error_key: error_key, + message: message, + response_code: response.code, + response_body: response.body, + } end - end - end end end diff --git a/plugin.rb b/plugin.rb index e372a37..3cf3e4c 100644 --- a/plugin.rb +++ b/plugin.rb @@ -26,7 +26,7 @@ after_initialize do Jobs.enqueue_in(time, :notify_chats, post_id: post.id) end - add_admin_route 'chat_integration.menu_title', 'chat-integration' + add_admin_route "chat_integration.menu_title", "chat-integration" AdminDashboardData.add_problem_check do next if !SiteSetting.chat_integration_enabled @@ -47,7 +47,7 @@ after_initialize do DiscourseChatIntegration::Provider.mount_engines if defined?(DiscourseAutomation) - add_automation_scriptable('send_slack_message') do + add_automation_scriptable("send_slack_message") do field :message, component: :message, required: true, accepts_placeholders: true field :url, component: :text, required: true field :channel, component: :text, required: true @@ -59,16 +59,24 @@ after_initialize do script do |context, fields, automation| sender = Discourse.system_user - content = fields.dig('message', 'value') - url = fields.dig('url', 'value') + content = fields.dig("message", "value") + url = fields.dig("url", "value") full_content = "#{content} - #{url}" - channel_name = fields.dig('channel', 'value') - channel = DiscourseChatIntegration::Channel.new(provider: "slack", data: { identifier: "##{channel_name}" }) + channel_name = fields.dig("channel", "value") + channel = + DiscourseChatIntegration::Channel.new( + provider: "slack", + data: { + identifier: "##{channel_name}", + }, + ) icon_url = if SiteSetting.chat_integration_slack_icon_url.present? "#{Discourse.base_url}#{SiteSetting.chat_integration_slack_icon_url}" - elsif (url = (SiteSetting.try(:site_logo_small_url) || SiteSetting.logo_small_url)).present? + elsif ( + url = (SiteSetting.try(:site_logo_small_url) || SiteSetting.logo_small_url) + ).present? "#{Discourse.base_url}#{url}" end @@ -83,7 +91,7 @@ after_initialize do channel: "##{channel_name}", username: slack_username, icon_url: icon_url, - attachments: [] + attachments: [], } summary = { @@ -94,7 +102,7 @@ after_initialize do mrkdwn_in: ["text"], title: content.truncate(100), title_link: url, - thumb_url: nil + thumb_url: nil, } message[:attachments].push(summary) diff --git a/spec/dummy_provider.rb b/spec/dummy_provider.rb index e9cd18e..c14832f 100644 --- a/spec/dummy_provider.rb +++ b/spec/dummy_provider.rb @@ -11,9 +11,7 @@ RSpec.shared_context "with dummy provider" do @@raise_exception = nil def self.trigger_notification(post, channel, rule) - if @@raise_exception - raise @@raise_exception - end + raise @@raise_exception if @@raise_exception @@sent_messages.push(post: post.id, channel: channel) end @@ -32,9 +30,7 @@ RSpec.shared_context "with dummy provider" do end end - after(:each) do - ::DiscourseChatIntegration::Provider.send(:remove_const, :DummyProvider) - end + after(:each) { ::DiscourseChatIntegration::Provider.send(:remove_const, :DummyProvider) } let(:provider) { ::DiscourseChatIntegration::Provider::DummyProvider } end @@ -44,9 +40,7 @@ RSpec.shared_context "with validated dummy provider" do module ::DiscourseChatIntegration::Provider::Dummy2Provider PROVIDER_NAME = "dummy2".freeze PROVIDER_ENABLED_SETTING = :chat_integration_enabled # Tie to main plugin enabled setting - CHANNEL_PARAMETERS = [ - { key: "val", regex: '^\S+$', unique: true } - ] + CHANNEL_PARAMETERS = [{ key: "val", regex: '^\S+$', unique: true }] @@sent_messages = [] @@ -58,10 +52,7 @@ RSpec.shared_context "with validated dummy provider" do @@sent_messages end end - end - after(:each) do - ::DiscourseChatIntegration::Provider.send(:remove_const, :Dummy2Provider) - end + after(:each) { ::DiscourseChatIntegration::Provider.send(:remove_const, :Dummy2Provider) } end diff --git a/spec/helpers/helper_spec.rb b/spec/helpers/helper_spec.rb index e830dd7..e94384d 100644 --- a/spec/helpers/helper_spec.rb +++ b/spec/helpers/helper_spec.rb @@ -1,229 +1,279 @@ # frozen_string_literal: true -require 'rails_helper' -require_relative '../dummy_provider' +require "rails_helper" +require_relative "../dummy_provider" RSpec.describe DiscourseChatIntegration::Manager do include_context "with dummy provider" - let(:chan1) { DiscourseChatIntegration::Channel.create!(provider: 'dummy') } - let(:chan2) { DiscourseChatIntegration::Channel.create!(provider: 'dummy') } + let(:chan1) { DiscourseChatIntegration::Channel.create!(provider: "dummy") } + let(:chan2) { DiscourseChatIntegration::Channel.create!(provider: "dummy") } let(:category) { Fabricate(:category) } let(:tag1) { Fabricate(:tag) } let(:tag2) { Fabricate(:tag) } let(:tag3) { Fabricate(:tag) } - describe '.process_command' do - - describe 'add new rule' do + describe ".process_command" do + describe "add new rule" do # Not testing how filters are merged here, that's done in .smart_create_rule # We just want to make sure the commands are being interpretted correctly - it 'should add a new rule correctly' do - response = DiscourseChatIntegration::Helper.process_command(chan1, ['watch', category.slug]) + it "should add a new rule correctly" do + response = DiscourseChatIntegration::Helper.process_command(chan1, ["watch", category.slug]) expect(response).to eq(I18n.t("chat_integration.provider.dummy.create.created")) rule = DiscourseChatIntegration::Rule.all.first expect(rule.channel).to eq(chan1) - expect(rule.filter).to eq('watch') + expect(rule.filter).to eq("watch") expect(rule.category_id).to eq(category.id) expect(rule.tags).to eq(nil) end - it 'should work with all four filter types' do - response = DiscourseChatIntegration::Helper.process_command(chan1, ['thread', category.slug]) + it "should work with all four filter types" do + response = + DiscourseChatIntegration::Helper.process_command(chan1, ["thread", category.slug]) + + rule = DiscourseChatIntegration::Rule.all.first + expect(rule.filter).to eq("thread") + + response = DiscourseChatIntegration::Helper.process_command(chan1, ["watch", category.slug]) + + rule = DiscourseChatIntegration::Rule.all.first + expect(rule.filter).to eq("watch") + + response = + DiscourseChatIntegration::Helper.process_command(chan1, ["follow", category.slug]) + + rule = DiscourseChatIntegration::Rule.all.first + expect(rule.filter).to eq("follow") + + response = DiscourseChatIntegration::Helper.process_command(chan1, ["mute", category.slug]) + + rule = DiscourseChatIntegration::Rule.all.first + expect(rule.filter).to eq("mute") + end + + it "errors on incorrect categories" do + response = DiscourseChatIntegration::Helper.process_command(chan1, %w[watch blah]) + + expect(response).to eq( + I18n.t( + "chat_integration.provider.dummy.not_found.category", + name: "blah", + list: "uncategorized", + ), + ) + end + + context "with tags enabled" do + before { SiteSetting.tagging_enabled = true } + + it "should add a new tag rule correctly" do + response = + DiscourseChatIntegration::Helper.process_command(chan1, ["watch", "tag:#{tag1.name}"]) + + expect(response).to eq(I18n.t("chat_integration.provider.dummy.create.created")) rule = DiscourseChatIntegration::Rule.all.first - expect(rule.filter).to eq('thread') - - response = DiscourseChatIntegration::Helper.process_command(chan1, ['watch', category.slug]) - - rule = DiscourseChatIntegration::Rule.all.first - expect(rule.filter).to eq('watch') - - response = DiscourseChatIntegration::Helper.process_command(chan1, ['follow', category.slug]) - - rule = DiscourseChatIntegration::Rule.all.first - expect(rule.filter).to eq('follow') - - response = DiscourseChatIntegration::Helper.process_command(chan1, ['mute', category.slug]) - - rule = DiscourseChatIntegration::Rule.all.first - expect(rule.filter).to eq('mute') + expect(rule.channel).to eq(chan1) + expect(rule.filter).to eq("watch") + expect(rule.category_id).to eq(nil) + expect(rule.tags).to eq([tag1.name]) end - it 'errors on incorrect categories' do - response = DiscourseChatIntegration::Helper.process_command(chan1, ['watch', 'blah']) + it "should work with a category and multiple tags" do + response = + DiscourseChatIntegration::Helper.process_command( + chan1, + ["watch", category.slug, "tag:#{tag1.name}", "tag:#{tag2.name}"], + ) - expect(response).to eq(I18n.t("chat_integration.provider.dummy.not_found.category", name: 'blah', list: 'uncategorized')) + expect(response).to eq(I18n.t("chat_integration.provider.dummy.create.created")) + + rule = DiscourseChatIntegration::Rule.all.first + expect(rule.channel).to eq(chan1) + expect(rule.filter).to eq("watch") + expect(rule.category_id).to eq(category.id) + expect(rule.tags).to contain_exactly(tag1.name, tag2.name) end - context 'with tags enabled' do - before do - SiteSetting.tagging_enabled = true - end - - it 'should add a new tag rule correctly' do - response = DiscourseChatIntegration::Helper.process_command(chan1, ['watch', "tag:#{tag1.name}"]) - - expect(response).to eq(I18n.t("chat_integration.provider.dummy.create.created")) - - rule = DiscourseChatIntegration::Rule.all.first - expect(rule.channel).to eq(chan1) - expect(rule.filter).to eq('watch') - expect(rule.category_id).to eq(nil) - expect(rule.tags).to eq([tag1.name]) - end - - it 'should work with a category and multiple tags' do - - response = DiscourseChatIntegration::Helper.process_command(chan1, ['watch', category.slug, "tag:#{tag1.name}", "tag:#{tag2.name}"]) - - expect(response).to eq(I18n.t("chat_integration.provider.dummy.create.created")) - - rule = DiscourseChatIntegration::Rule.all.first - expect(rule.channel).to eq(chan1) - expect(rule.filter).to eq('watch') - expect(rule.category_id).to eq(category.id) - expect(rule.tags).to contain_exactly(tag1.name, tag2.name) - end - - it 'errors on incorrect tags' do - response = DiscourseChatIntegration::Helper.process_command(chan1, ['watch', category.slug, "tag:blah"]) - expect(response).to eq(I18n.t("chat_integration.provider.dummy.not_found.tag", name: "blah")) - end + it "errors on incorrect tags" do + response = + DiscourseChatIntegration::Helper.process_command( + chan1, + ["watch", category.slug, "tag:blah"], + ) + expect(response).to eq( + I18n.t("chat_integration.provider.dummy.not_found.tag", name: "blah"), + ) end + end end - describe 'remove rule' do - it 'removes the rule' do - rule1 = DiscourseChatIntegration::Rule.create(channel: chan1, - filter: 'watch', - category_id: category.id, - tags: [tag1.name, tag2.name] - ) + describe "remove rule" do + it "removes the rule" do + rule1 = + DiscourseChatIntegration::Rule.create( + channel: chan1, + filter: "watch", + category_id: category.id, + tags: [tag1.name, tag2.name], + ) - expect(DiscourseChatIntegration::Rule.all.size).to eq(1) + expect(DiscourseChatIntegration::Rule.all.size).to eq(1) - response = DiscourseChatIntegration::Helper.process_command(chan1, ['remove', '1']) + response = DiscourseChatIntegration::Helper.process_command(chan1, %w[remove 1]) - expect(response).to eq(I18n.t("chat_integration.provider.dummy.delete.success")) + expect(response).to eq(I18n.t("chat_integration.provider.dummy.delete.success")) - expect(DiscourseChatIntegration::Rule.all.size).to eq(0) - end - - it 'errors correctly' do - response = DiscourseChatIntegration::Helper.process_command(chan1, ['remove', '1']) - expect(response).to eq(I18n.t("chat_integration.provider.dummy.delete.error")) - end + expect(DiscourseChatIntegration::Rule.all.size).to eq(0) end - describe 'help command' do - it 'should return the right response' do - response = DiscourseChatIntegration::Helper.process_command(chan1, ["help"]) - expect(response).to eq(I18n.t("chat_integration.provider.dummy.help")) - end + it "errors correctly" do + response = DiscourseChatIntegration::Helper.process_command(chan1, %w[remove 1]) + expect(response).to eq(I18n.t("chat_integration.provider.dummy.delete.error")) end + end - describe 'status command' do - it 'should return the right response' do - response = DiscourseChatIntegration::Helper.process_command(chan1, ['status']) - expect(response).to eq(DiscourseChatIntegration::Helper.status_for_channel(chan1)) - end + describe "help command" do + it "should return the right response" do + response = DiscourseChatIntegration::Helper.process_command(chan1, ["help"]) + expect(response).to eq(I18n.t("chat_integration.provider.dummy.help")) end + end - describe 'unknown command' do - it 'should return the right response' do - response = DiscourseChatIntegration::Helper.process_command(chan1, ['somerandomtext']) - expect(response).to eq(I18n.t("chat_integration.provider.dummy.parse_error")) - end + describe "status command" do + it "should return the right response" do + response = DiscourseChatIntegration::Helper.process_command(chan1, ["status"]) + expect(response).to eq(DiscourseChatIntegration::Helper.status_for_channel(chan1)) end + end + + describe "unknown command" do + it "should return the right response" do + response = DiscourseChatIntegration::Helper.process_command(chan1, ["somerandomtext"]) + expect(response).to eq(I18n.t("chat_integration.provider.dummy.parse_error")) + end + end end - describe '.status_for_channel' do - - context 'with no rules' do - it 'includes the heading' do + describe ".status_for_channel" do + context "with no rules" do + it "includes the heading" do string = DiscourseChatIntegration::Helper.status_for_channel(chan1) - expect(string).to include('dummy.status.header') + expect(string).to include("dummy.status.header") end - it 'includes the no_rules string' do + it "includes the no_rules string" do string = DiscourseChatIntegration::Helper.status_for_channel(chan1) - expect(string).to include('dummy.status.no_rules') + expect(string).to include("dummy.status.no_rules") end end - context 'with some rules' do + context "with some rules" do let(:group) { Fabricate(:group) } before do - DiscourseChatIntegration::Rule.create!(channel: chan1, filter: 'watch', category_id: category.id, tags: nil) - DiscourseChatIntegration::Rule.create!(channel: chan1, filter: 'mute', category_id: nil, tags: nil) - DiscourseChatIntegration::Rule.create!(channel: chan1, filter: 'follow', category_id: nil, tags: [tag1.name]) - DiscourseChatIntegration::Rule.create!(channel: chan1, filter: 'watch', type: 'group_message', group_id: group.id) - DiscourseChatIntegration::Rule.create!(channel: chan2, filter: 'watch', category_id: 1, tags: nil) + DiscourseChatIntegration::Rule.create!( + channel: chan1, + filter: "watch", + category_id: category.id, + tags: nil, + ) + DiscourseChatIntegration::Rule.create!( + channel: chan1, + filter: "mute", + category_id: nil, + tags: nil, + ) + DiscourseChatIntegration::Rule.create!( + channel: chan1, + filter: "follow", + category_id: nil, + tags: [tag1.name], + ) + DiscourseChatIntegration::Rule.create!( + channel: chan1, + filter: "watch", + type: "group_message", + group_id: group.id, + ) + DiscourseChatIntegration::Rule.create!( + channel: chan2, + filter: "watch", + category_id: 1, + tags: nil, + ) SiteSetting.tagging_enabled = false end - it 'displays the correct rules' do + it "displays the correct rules" do string = DiscourseChatIntegration::Helper.status_for_channel(chan1) - expect(string.scan('status.rule_string').size).to eq(4) + expect(string.scan("status.rule_string").size).to eq(4) end - it 'only displays tags for rules with tags' do + it "only displays tags for rules with tags" do string = DiscourseChatIntegration::Helper.status_for_channel(chan1) - expect(string.scan('rule_string_tags_suffix').size).to eq(0) + expect(string.scan("rule_string_tags_suffix").size).to eq(0) SiteSetting.tagging_enabled = true string = DiscourseChatIntegration::Helper.status_for_channel(chan1) - expect(string.scan('rule_string_tags_suffix').size).to eq(1) + expect(string.scan("rule_string_tags_suffix").size).to eq(1) end - end - end - describe '.delete_by_index' do + describe ".delete_by_index" do let(:category2) { Fabricate(:category) } let(:category3) { Fabricate(:category) } - it 'deletes the correct rule' do + it "deletes the correct rule" do # Three identical rules, with different filters # Status will be sorted by precedence # be in this order - rule1 = DiscourseChatIntegration::Rule.create(channel: chan1, - filter: 'mute', - category_id: category.id, - tags: [tag1.name, tag2.name] - ) - rule2 = DiscourseChatIntegration::Rule.create(channel: chan1, - filter: 'watch', - category_id: category2.id, - tags: [tag1.name, tag2.name] - ) - rule3 = DiscourseChatIntegration::Rule.create(channel: chan1, - filter: 'follow', - category_id: category3.id, - tags: [tag1.name, tag2.name] - ) + rule1 = + DiscourseChatIntegration::Rule.create( + channel: chan1, + filter: "mute", + category_id: category.id, + tags: [tag1.name, tag2.name], + ) + rule2 = + DiscourseChatIntegration::Rule.create( + channel: chan1, + filter: "watch", + category_id: category2.id, + tags: [tag1.name, tag2.name], + ) + rule3 = + DiscourseChatIntegration::Rule.create( + channel: chan1, + filter: "follow", + category_id: category3.id, + tags: [tag1.name, tag2.name], + ) expect(DiscourseChatIntegration::Rule.all.size).to eq(3) expect(DiscourseChatIntegration::Helper.delete_by_index(chan1, 2)).to eq(:deleted) expect(DiscourseChatIntegration::Rule.all.size).to eq(2) - expect(DiscourseChatIntegration::Rule.all.map(&:category_id)).to contain_exactly(category.id, category3.id) + expect(DiscourseChatIntegration::Rule.all.map(&:category_id)).to contain_exactly( + category.id, + category3.id, + ) end - it 'fails gracefully for out of range indexes' do - rule1 = DiscourseChatIntegration::Rule.create(channel: chan1, - filter: 'watch', - category_id: category.id, - tags: [tag1.name, tag2.name] - ) + it "fails gracefully for out of range indexes" do + rule1 = + DiscourseChatIntegration::Rule.create( + channel: chan1, + filter: "watch", + category_id: category.id, + tags: [tag1.name, tag2.name], + ) expect(DiscourseChatIntegration::Helper.delete_by_index(chan1, -1)).to eq(false) expect(DiscourseChatIntegration::Helper.delete_by_index(chan1, 0)).to eq(false) @@ -231,87 +281,100 @@ RSpec.describe DiscourseChatIntegration::Manager do expect(DiscourseChatIntegration::Helper.delete_by_index(chan1, 1)).to eq(:deleted) end - end - describe '.smart_create_rule' do - - it 'creates a rule when there are none' do - val = DiscourseChatIntegration::Helper.smart_create_rule(channel: chan1, - filter: 'watch', - category_id: category.id, - tags: [tag1.name] - ) + describe ".smart_create_rule" do + it "creates a rule when there are none" do + val = + DiscourseChatIntegration::Helper.smart_create_rule( + channel: chan1, + filter: "watch", + category_id: category.id, + tags: [tag1.name], + ) expect(val).to eq(:created) record = DiscourseChatIntegration::Rule.all.first expect(record.channel).to eq(chan1) - expect(record.filter).to eq('watch') + expect(record.filter).to eq("watch") expect(record.category_id).to eq(category.id) expect(record.tags).to eq([tag1.name]) end - it 'updates a rule when it has the same category and tags' do - existing = DiscourseChatIntegration::Rule.create!(channel: chan1, - filter: 'watch', - category_id: category.id, - tags: [tag2.name, tag1.name] - ) + it "updates a rule when it has the same category and tags" do + existing = + DiscourseChatIntegration::Rule.create!( + channel: chan1, + filter: "watch", + category_id: category.id, + tags: [tag2.name, tag1.name], + ) - val = DiscourseChatIntegration::Helper.smart_create_rule(channel: chan1, - filter: 'mute', - category_id: category.id, - tags: [tag1.name, tag2.name] - ) + val = + DiscourseChatIntegration::Helper.smart_create_rule( + channel: chan1, + filter: "mute", + category_id: category.id, + tags: [tag1.name, tag2.name], + ) expect(val).to eq(:updated) expect(DiscourseChatIntegration::Rule.all.size).to eq(1) - expect(DiscourseChatIntegration::Rule.all.first.filter).to eq('mute') + expect(DiscourseChatIntegration::Rule.all.first.filter).to eq("mute") end - it 'updates a rule when it has the same category and filter' do - existing = DiscourseChatIntegration::Rule.create(channel: chan1, - filter: 'watch', - category_id: category.id, - tags: [tag1.name, tag2.name] - ) + it "updates a rule when it has the same category and filter" do + existing = + DiscourseChatIntegration::Rule.create( + channel: chan1, + filter: "watch", + category_id: category.id, + tags: [tag1.name, tag2.name], + ) - val = DiscourseChatIntegration::Helper.smart_create_rule(channel: chan1, - filter: 'watch', - category_id: category.id, - tags: [tag1.name, tag3.name] - ) + val = + DiscourseChatIntegration::Helper.smart_create_rule( + channel: chan1, + filter: "watch", + category_id: category.id, + tags: [tag1.name, tag3.name], + ) expect(val).to eq(:updated) expect(DiscourseChatIntegration::Rule.all.size).to eq(1) - expect(DiscourseChatIntegration::Rule.all.first.tags).to contain_exactly(tag1.name, tag2.name, tag3.name) + expect(DiscourseChatIntegration::Rule.all.first.tags).to contain_exactly( + tag1.name, + tag2.name, + tag3.name, + ) end - it 'destroys duplicate rules on save' do - DiscourseChatIntegration::Rule.create!(channel: chan1, filter: 'watch') - DiscourseChatIntegration::Rule.create!(channel: chan1, filter: 'watch') + it "destroys duplicate rules on save" do + DiscourseChatIntegration::Rule.create!(channel: chan1, filter: "watch") + DiscourseChatIntegration::Rule.create!(channel: chan1, filter: "watch") expect(DiscourseChatIntegration::Rule.all.size).to eq(2) - val = DiscourseChatIntegration::Helper.smart_create_rule(channel: chan1, - filter: 'watch', - category_id: nil, - tags: nil - ) + val = + DiscourseChatIntegration::Helper.smart_create_rule( + channel: chan1, + filter: "watch", + category_id: nil, + tags: nil, + ) expect(val).to eq(:updated) expect(DiscourseChatIntegration::Rule.all.size).to eq(1) end - it 'returns false on error' do - val = DiscourseChatIntegration::Helper.smart_create_rule(channel: chan1, filter: 'blah') + it "returns false on error" do + val = DiscourseChatIntegration::Helper.smart_create_rule(channel: chan1, filter: "blah") expect(val).to eq(false) end end - describe '.save_transcript' do - - it 'saves a transcript to redis' do + describe ".save_transcript" do + it "saves a transcript to redis" do key = DiscourseChatIntegration::Helper.save_transcript("Some content here") expect(Discourse.redis.get("chat_integration:transcript:#{key}")).to eq("Some content here") @@ -325,19 +388,25 @@ RSpec.describe DiscourseChatIntegration::Manager do end describe ".formatted_display_name" do - let(:user) { Fabricate(:user, name: "John Smith", username: 'js1') } + let(:user) { Fabricate(:user, name: "John Smith", username: "js1") } it "prioritizes correctly" do SiteSetting.prioritize_username_in_ux = true - expect(DiscourseChatIntegration::Helper.formatted_display_name(user)).to eq("@#{user.username} (John Smith)") + expect(DiscourseChatIntegration::Helper.formatted_display_name(user)).to eq( + "@#{user.username} (John Smith)", + ) SiteSetting.prioritize_username_in_ux = false - expect(DiscourseChatIntegration::Helper.formatted_display_name(user)).to eq("John Smith (@#{user.username})") + expect(DiscourseChatIntegration::Helper.formatted_display_name(user)).to eq( + "John Smith (@#{user.username})", + ) end it "only displays one when name/username are similar" do user.update!(username: "john_smith") SiteSetting.prioritize_username_in_ux = true - expect(DiscourseChatIntegration::Helper.formatted_display_name(user)).to eq("@#{user.username}") + expect(DiscourseChatIntegration::Helper.formatted_display_name(user)).to eq( + "@#{user.username}", + ) SiteSetting.prioritize_username_in_ux = false expect(DiscourseChatIntegration::Helper.formatted_display_name(user)).to eq("John Smith") end @@ -346,10 +415,13 @@ RSpec.describe DiscourseChatIntegration::Manager do SiteSetting.enable_names = false SiteSetting.prioritize_username_in_ux = true - expect(DiscourseChatIntegration::Helper.formatted_display_name(user)).to eq("@#{user.username}") + expect(DiscourseChatIntegration::Helper.formatted_display_name(user)).to eq( + "@#{user.username}", + ) SiteSetting.prioritize_username_in_ux = false - expect(DiscourseChatIntegration::Helper.formatted_display_name(user)).to eq("@#{user.username}") + expect(DiscourseChatIntegration::Helper.formatted_display_name(user)).to eq( + "@#{user.username}", + ) end end - end diff --git a/spec/jobs/onceoff/migrate_from_slack_official_spec.rb b/spec/jobs/onceoff/migrate_from_slack_official_spec.rb index 8acf7fc..e52fb91 100644 --- a/spec/jobs/onceoff/migrate_from_slack_official_spec.rb +++ b/spec/jobs/onceoff/migrate_from_slack_official_spec.rb @@ -1,42 +1,46 @@ # frozen_string_literal: true -require 'rails_helper' +require "rails_helper" RSpec.describe Jobs::DiscourseChatMigrateFromSlackOfficial do let(:category) { Fabricate(:category) } - describe 'site settings' do + describe "site settings" do before do PluginStoreRow.create!( - plugin_name: 'discourse-slack-official', + plugin_name: "discourse-slack-official", key: "category_#{category.id}", type_name: "JSON", - value: "[{\"channel\":\"#slack-channel\",\"filter\":\"mute\"}]" + value: "[{\"channel\":\"#slack-channel\",\"filter\":\"mute\"}]", ) - SiteSetting.create!(value: 't', data_type: 5, name: 'slack_enabled') - SiteSetting.create!(value: 'token', data_type: 1, name: 'slack_access_token') - SiteSetting.create!(value: 'token2', data_type: 1, name: 'slack_incoming_webhook_token') - SiteSetting.create!(value: 300, data_type: 3, name: 'slack_discourse_excerpt_length') - SiteSetting.create!(value: "https://hooks.slack.com/services/something", data_type: 1, name: 'slack_outbound_webhook_url') - SiteSetting.create!(value: "http://outbound2.com", data_type: 1, name: 'slack_icon_url') - SiteSetting.create!(value: 100, data_type: 3, name: 'post_to_slack_window_secs') - SiteSetting.create!(value: User.last.username, data_type: 1, name: 'slack_discourse_username') + SiteSetting.create!(value: "t", data_type: 5, name: "slack_enabled") + SiteSetting.create!(value: "token", data_type: 1, name: "slack_access_token") + SiteSetting.create!(value: "token2", data_type: 1, name: "slack_incoming_webhook_token") + SiteSetting.create!(value: 300, data_type: 3, name: "slack_discourse_excerpt_length") + SiteSetting.create!( + value: "https://hooks.slack.com/services/something", + data_type: 1, + name: "slack_outbound_webhook_url", + ) + SiteSetting.create!(value: "http://outbound2.com", data_type: 1, name: "slack_icon_url") + SiteSetting.create!(value: 100, data_type: 3, name: "post_to_slack_window_secs") + SiteSetting.create!(value: User.last.username, data_type: 1, name: "slack_discourse_username") end - it 'should migrate the site settings correctly' do + it "should migrate the site settings correctly" do described_class.new.execute_onceoff({}) - expect(SiteSetting.find_by(name: 'slack_enabled').value).to eq('f') - expect(SiteSetting.chat_integration_slack_access_token).to eq('token') - expect(SiteSetting.chat_integration_slack_incoming_webhook_token).to eq('token2') + expect(SiteSetting.find_by(name: "slack_enabled").value).to eq("f") + expect(SiteSetting.chat_integration_slack_access_token).to eq("token") + expect(SiteSetting.chat_integration_slack_incoming_webhook_token).to eq("token2") expect(SiteSetting.chat_integration_slack_excerpt_length).to eq(300) - expect(SiteSetting.chat_integration_slack_outbound_webhook_url) - .to eq("https://hooks.slack.com/services/something") + expect(SiteSetting.chat_integration_slack_outbound_webhook_url).to eq( + "https://hooks.slack.com/services/something", + ) - expect(SiteSetting.chat_integration_slack_icon_url) - .to eq("http://outbound2.com") + expect(SiteSetting.chat_integration_slack_icon_url).to eq("http://outbound2.com") expect(SiteSetting.chat_integration_delay_seconds).to eq(100) expect(SiteSetting.chat_integration_discourse_username).to eq(User.last.username) @@ -44,31 +48,31 @@ RSpec.describe Jobs::DiscourseChatMigrateFromSlackOfficial do expect(SiteSetting.chat_integration_enabled).to eq(true) end - describe 'when slack_discourse_username is not valid' do - before do - SiteSetting.find_by(name: 'slack_discourse_username').update!(value: 'someguy') - end + describe "when slack_discourse_username is not valid" do + before { SiteSetting.find_by(name: "slack_discourse_username").update!(value: "someguy") } - it 'should default to the system user' do + it "should default to the system user" do described_class.new.execute_onceoff({}) - expect(SiteSetting.chat_integration_discourse_username) - .to eq(Discourse.system_user.username) + expect(SiteSetting.chat_integration_discourse_username).to eq( + Discourse.system_user.username, + ) end end end - describe 'when a uncategorized filter is present' do + describe "when a uncategorized filter is present" do before do PluginStoreRow.create!( - plugin_name: 'discourse-slack-official', + plugin_name: "discourse-slack-official", key: "category_*", type_name: "JSON", - value: "[{\"channel\":\"#channel1\",\"filter\":\"watch\"},{\"channel\":\"channel2\",\"filter\":\"follow\"},{\"channel\":\"#channel1\",\"filter\":\"mute\"}]" + value: + "[{\"channel\":\"#channel1\",\"filter\":\"watch\"},{\"channel\":\"channel2\",\"filter\":\"follow\"},{\"channel\":\"#channel1\",\"filter\":\"mute\"}]", ) end - it 'should create the right channels and rules' do + it "should create the right channels and rules" do described_class.new.execute_onceoff({}) expect(DiscourseChatIntegration::Channel.count).to eq(2) @@ -76,86 +80,87 @@ RSpec.describe Jobs::DiscourseChatMigrateFromSlackOfficial do channel = DiscourseChatIntegration::Channel.first - expect(channel.value['provider']).to eq("slack") - expect(channel.value['data']['identifier']).to eq("#channel1") + expect(channel.value["provider"]).to eq("slack") + expect(channel.value["data"]["identifier"]).to eq("#channel1") rule = DiscourseChatIntegration::Rule.first - expect(rule.value['channel_id']).to eq(channel.id) - expect(rule.value['filter']).to eq('mute') - expect(rule.value['category_id']).to eq(nil) + expect(rule.value["channel_id"]).to eq(channel.id) + expect(rule.value["filter"]).to eq("mute") + expect(rule.value["category_id"]).to eq(nil) channel = DiscourseChatIntegration::Channel.last - expect(channel.value['provider']).to eq("slack") - expect(channel.value['data']['identifier']).to eq("#channel2") + expect(channel.value["provider"]).to eq("slack") + expect(channel.value["data"]["identifier"]).to eq("#channel2") rule = DiscourseChatIntegration::Rule.last - expect(rule.value['channel_id']).to eq(channel.id) - expect(rule.value['filter']).to eq('follow') - expect(rule.value['category_id']).to eq(nil) + expect(rule.value["channel_id"]).to eq(channel.id) + expect(rule.value["filter"]).to eq("follow") + expect(rule.value["category_id"]).to eq(nil) end end - describe 'when filter contains an invalid tag' do + describe "when filter contains an invalid tag" do let(:tag) { Fabricate(:tag) } before do PluginStoreRow.create!( - plugin_name: 'discourse-slack-official', + plugin_name: "discourse-slack-official", key: "category_#{category.id}", type_name: "JSON", - value: "[{\"channel\":\"#slack-channel\",\"filter\":\"mute\",\"tags\":[\"#{tag.name}\",\"random-tag\"]}]" + value: + "[{\"channel\":\"#slack-channel\",\"filter\":\"mute\",\"tags\":[\"#{tag.name}\",\"random-tag\"]}]", ) end - it 'should discard invalid tags' do + it "should discard invalid tags" do described_class.new.execute_onceoff({}) rule = DiscourseChatIntegration::Rule.first - expect(rule.value['tags']).to eq([tag.name]) + expect(rule.value["tags"]).to eq([tag.name]) end end - describe 'when a category filter is present' do + describe "when a category filter is present" do before do PluginStoreRow.create!( - plugin_name: 'discourse-slack-official', + plugin_name: "discourse-slack-official", key: "category_#{category.id}", type_name: "JSON", - value: "[{\"channel\":\"#slack-channel\",\"filter\":\"mute\"}]" + value: "[{\"channel\":\"#slack-channel\",\"filter\":\"mute\"}]", ) end - it 'should migrate the settings correctly' do + it "should migrate the settings correctly" do described_class.new.execute_onceoff({}) channel = DiscourseChatIntegration::Channel.first - expect(channel.value['provider']).to eq("slack") - expect(channel.value['data']['identifier']).to eq("#slack-channel") + expect(channel.value["provider"]).to eq("slack") + expect(channel.value["data"]["identifier"]).to eq("#slack-channel") rule = DiscourseChatIntegration::Rule.first - expect(rule.value['channel_id']).to eq(channel.id) - expect(rule.value['filter']).to eq('mute') - expect(rule.value['category_id']).to eq(category.id) + expect(rule.value["channel_id"]).to eq(channel.id) + expect(rule.value["filter"]).to eq("mute") + expect(rule.value["category_id"]).to eq(category.id) end end - describe 'when a category has been deleted' do + describe "when a category has been deleted" do before do PluginStoreRow.create!( - plugin_name: 'discourse-slack-official', - key: 'category_9999', + plugin_name: "discourse-slack-official", + key: "category_9999", type_name: "JSON", - value: "[{\"channel\":\"#slack-channel\",\"filter\":\"mute\"}]" + value: "[{\"channel\":\"#slack-channel\",\"filter\":\"mute\"}]", ) end - it 'should not migrate the settings' do + it "should not migrate the settings" do described_class.new.execute_onceoff({}) expect(DiscourseChatIntegration::Channel.count).to eq(0) diff --git a/spec/jobs/regular/notify_chats_spec.rb b/spec/jobs/regular/notify_chats_spec.rb index dec9ee8..bc3bc09 100644 --- a/spec/jobs/regular/notify_chats_spec.rb +++ b/spec/jobs/regular/notify_chats_spec.rb @@ -1,48 +1,36 @@ # frozen_string_literal: true -require 'rails_helper' +require "rails_helper" RSpec.describe PostCreator do let(:topic) { Fabricate(:post).topic } - before do - Jobs::NotifyChats.jobs.clear - end + before { Jobs::NotifyChats.jobs.clear } - describe 'when a post is created' do - describe 'when plugin is enabled' do - before do - SiteSetting.chat_integration_enabled = true - end + describe "when a post is created" do + describe "when plugin is enabled" do + before { SiteSetting.chat_integration_enabled = true } - it 'should schedule a chat notification job' do + it "should schedule a chat notification job" do freeze_time Time.now.beginning_of_day - post = PostCreator.new(topic.user, - raw: 'Some post content', - topic_id: topic.id - ).create! + post = PostCreator.new(topic.user, raw: "Some post content", topic_id: topic.id).create! job = Jobs::NotifyChats.jobs.last - expect(job['at']) - .to eq(Time.now.to_f + SiteSetting.chat_integration_delay_seconds.seconds.to_f) - - expect(job['args'].first['post_id']).to eq(post.id) + expect(job["at"]).to eq( + Time.now.to_f + SiteSetting.chat_integration_delay_seconds.seconds.to_f, + ) + expect(job["args"].first["post_id"]).to eq(post.id) end end - describe 'when plugin is not enabled' do - before do - SiteSetting.chat_integration_enabled = false - end + describe "when plugin is not enabled" do + before { SiteSetting.chat_integration_enabled = false } - it 'should not schedule a job for chat notifications' do - PostCreator.new(topic.user, - raw: 'Some post content', - topic_id: topic.id - ).create! + it "should not schedule a job for chat notifications" do + PostCreator.new(topic.user, raw: "Some post content", topic_id: topic.id).create! expect(Jobs::NotifyChats.jobs).to eq([]) end diff --git a/spec/lib/discourse_chat_integration/provider/discord/discord_provider_spec.rb b/spec/lib/discourse_chat_integration/provider/discord/discord_provider_spec.rb index 1007e07..2dde375 100644 --- a/spec/lib/discourse_chat_integration/provider/discord/discord_provider_spec.rb +++ b/spec/lib/discourse_chat_integration/provider/discord/discord_provider_spec.rb @@ -1,38 +1,52 @@ # frozen_string_literal: true -require 'rails_helper' +require "rails_helper" RSpec.describe DiscourseChatIntegration::Provider::DiscordProvider do let(:post) { Fabricate(:post) } - describe '.trigger_notifications' do - before do - SiteSetting.chat_integration_discord_enabled = true + describe ".trigger_notifications" do + before { SiteSetting.chat_integration_discord_enabled = true } + + let(:chan1) do + DiscourseChatIntegration::Channel.create!( + provider: "discord", + data: { + name: "Awesome Channel", + webhook_url: "https://discord.com/api/webhooks/1234/abcd", + }, + ) end - let(:chan1) { DiscourseChatIntegration::Channel.create!(provider: 'discord', data: { name: "Awesome Channel", webhook_url: 'https://discord.com/api/webhooks/1234/abcd' }) } - - it 'sends a webhook request' do - stub1 = stub_request(:post, 'https://discord.com/api/webhooks/1234/abcd?wait=true').to_return(status: 200) + it "sends a webhook request" do + stub1 = + stub_request(:post, "https://discord.com/api/webhooks/1234/abcd?wait=true").to_return( + status: 200, + ) described_class.trigger_notification(post, chan1, nil) expect(stub1).to have_been_requested.once end - it 'includes the protocol in the avatar URL' do - stub1 = stub_request(:post, 'https://discord.com/api/webhooks/1234/abcd?wait=true') - .with(body: hash_including(embeds: [hash_including(author: hash_including(url: /^https?:\/\//))])) - .to_return(status: 200) + it "includes the protocol in the avatar URL" do + stub1 = + stub_request(:post, "https://discord.com/api/webhooks/1234/abcd?wait=true").with( + body: + hash_including(embeds: [hash_including(author: hash_including(url: %r{^https?://}))]), + ).to_return(status: 200) described_class.trigger_notification(post, chan1, nil) expect(stub1).to have_been_requested.once end - it 'handles errors correctly' do - stub1 = stub_request(:post, "https://discord.com/api/webhooks/1234/abcd?wait=true").to_return(status: 400) + it "handles errors correctly" do + stub1 = + stub_request(:post, "https://discord.com/api/webhooks/1234/abcd?wait=true").to_return( + status: 400, + ) expect(stub1).to have_been_requested.times(0) - expect { described_class.trigger_notification(post, chan1, nil) }.to raise_exception(::DiscourseChatIntegration::ProviderError) + expect { described_class.trigger_notification(post, chan1, nil) }.to raise_exception( + ::DiscourseChatIntegration::ProviderError, + ) expect(stub1).to have_been_requested.once end - end - end diff --git a/spec/lib/discourse_chat_integration/provider/flowdock/flowdock_provider_spec.rb b/spec/lib/discourse_chat_integration/provider/flowdock/flowdock_provider_spec.rb index 8edd0e0..92b62b9 100644 --- a/spec/lib/discourse_chat_integration/provider/flowdock/flowdock_provider_spec.rb +++ b/spec/lib/discourse_chat_integration/provider/flowdock/flowdock_provider_spec.rb @@ -1,27 +1,38 @@ # frozen_string_literal: true -require 'rails_helper' +require "rails_helper" RSpec.describe DiscourseChatIntegration::Provider::FlowdockProvider do let(:post) { Fabricate(:post) } - describe '.trigger_notifications' do - before do - SiteSetting.chat_integration_flowdock_enabled = true + describe ".trigger_notifications" do + before { SiteSetting.chat_integration_flowdock_enabled = true } + + let(:chan1) do + DiscourseChatIntegration::Channel.create!( + provider: "flowdock", + data: { + flow_token: "5d1fe04cf66e078d6a2b579ddb8a465b", + }, + ) end - let(:chan1) { DiscourseChatIntegration::Channel.create!(provider: 'flowdock', data: { flow_token: '5d1fe04cf66e078d6a2b579ddb8a465b' }) } - - it 'sends a request' do + it "sends a request" do stub1 = stub_request(:post, "https://api.flowdock.com/messages").to_return(status: 200) described_class.trigger_notification(post, chan1, nil) expect(stub1).to have_been_requested.once end - it 'handles errors correctly' do - stub1 = stub_request(:post, "https://api.flowdock.com/messages").to_return(status: 404, body: "{ \"error\": \"Not Found\"}") + it "handles errors correctly" do + stub1 = + stub_request(:post, "https://api.flowdock.com/messages").to_return( + status: 404, + body: "{ \"error\": \"Not Found\"}", + ) expect(stub1).to have_been_requested.times(0) - expect { described_class.trigger_notification(post, chan1, nil) }.to raise_exception(::DiscourseChatIntegration::ProviderError) + expect { described_class.trigger_notification(post, chan1, nil) }.to raise_exception( + ::DiscourseChatIntegration::ProviderError, + ) expect(stub1).to have_been_requested.once end end diff --git a/spec/lib/discourse_chat_integration/provider/gitter/gitter_provider_spec.rb b/spec/lib/discourse_chat_integration/provider/gitter/gitter_provider_spec.rb index e27acd5..0914129 100644 --- a/spec/lib/discourse_chat_integration/provider/gitter/gitter_provider_spec.rb +++ b/spec/lib/discourse_chat_integration/provider/gitter/gitter_provider_spec.rb @@ -1,27 +1,39 @@ # frozen_string_literal: true -require 'rails_helper' +require "rails_helper" RSpec.describe DiscourseChatIntegration::Provider::GitterProvider do let(:post) { Fabricate(:post) } - describe '.trigger_notifications' do - before do - SiteSetting.chat_integration_gitter_enabled = true + describe ".trigger_notifications" do + before { SiteSetting.chat_integration_gitter_enabled = true } + + let(:chan1) do + DiscourseChatIntegration::Channel.create!( + provider: "gitter", + data: { + name: "gitterHQ/services", + webhook_url: "https://webhooks.gitter.im/e/a1e2i3o4u5", + }, + ) end - let(:chan1) { DiscourseChatIntegration::Channel.create!(provider: 'gitter', data: { name: 'gitterHQ/services', webhook_url: 'https://webhooks.gitter.im/e/a1e2i3o4u5' }) } - - it 'sends a webhook request' do - stub1 = stub_request(:post, chan1.data['webhook_url']).to_return(body: "OK") + it "sends a webhook request" do + stub1 = stub_request(:post, chan1.data["webhook_url"]).to_return(body: "OK") described_class.trigger_notification(post, chan1, nil) expect(stub1).to have_been_requested.once end - it 'handles errors correctly' do - stub1 = stub_request(:post, chan1.data['webhook_url']).to_return(status: 404, body: "{ \"error\": \"Not Found\"}") + it "handles errors correctly" do + stub1 = + stub_request(:post, chan1.data["webhook_url"]).to_return( + status: 404, + body: "{ \"error\": \"Not Found\"}", + ) expect(stub1).to have_been_requested.times(0) - expect { described_class.trigger_notification(post, chan1, nil) }.to raise_exception(::DiscourseChatIntegration::ProviderError) + expect { described_class.trigger_notification(post, chan1, nil) }.to raise_exception( + ::DiscourseChatIntegration::ProviderError, + ) expect(stub1).to have_been_requested.once end end diff --git a/spec/lib/discourse_chat_integration/provider/google/google_provider_spec.rb b/spec/lib/discourse_chat_integration/provider/google/google_provider_spec.rb index e3f54b5..80d1de1 100644 --- a/spec/lib/discourse_chat_integration/provider/google/google_provider_spec.rb +++ b/spec/lib/discourse_chat_integration/provider/google/google_provider_spec.rb @@ -1,30 +1,36 @@ # frozen_string_literal: true -require 'rails_helper' +require "rails_helper" RSpec.describe DiscourseChatIntegration::Provider::GoogleProvider do let(:post) { Fabricate(:post) } - describe '.trigger_notifications' do - before do - SiteSetting.chat_integration_google_enabled = true + describe ".trigger_notifications" do + before { SiteSetting.chat_integration_google_enabled = true } + + let(:chan1) do + DiscourseChatIntegration::Channel.create!( + provider: "google", + data: { + name: "discourse", + webhook_url: "https://chat.googleapis.com/v1/abcdefg", + }, + ) end - let(:chan1) { DiscourseChatIntegration::Channel.create!(provider: 'google', data: { name: 'discourse', webhook_url: 'https://chat.googleapis.com/v1/abcdefg' }) } - - it 'sends a webhook request' do - stub1 = stub_request(:post, chan1.data['webhook_url']).to_return(body: "1") + it "sends a webhook request" do + stub1 = stub_request(:post, chan1.data["webhook_url"]).to_return(body: "1") described_class.trigger_notification(post, chan1, nil) expect(stub1).to have_been_requested.once end - it 'handles errors correctly' do - stub1 = stub_request(:post, chan1.data['webhook_url']).to_return(status: 400, body: "{}") + it "handles errors correctly" do + stub1 = stub_request(:post, chan1.data["webhook_url"]).to_return(status: 400, body: "{}") expect(stub1).to have_been_requested.times(0) - expect { described_class.trigger_notification(post, chan1, nil) }.to raise_exception(::DiscourseChatIntegration::ProviderError) + expect { described_class.trigger_notification(post, chan1, nil) }.to raise_exception( + ::DiscourseChatIntegration::ProviderError, + ) expect(stub1).to have_been_requested.once end - end - end diff --git a/spec/lib/discourse_chat_integration/provider/groupme/groupme_provider_spect.rb b/spec/lib/discourse_chat_integration/provider/groupme/groupme_provider_spect.rb index 333450a..ffdccdf 100644 --- a/spec/lib/discourse_chat_integration/provider/groupme/groupme_provider_spect.rb +++ b/spec/lib/discourse_chat_integration/provider/groupme/groupme_provider_spect.rb @@ -1,28 +1,41 @@ # frozen_string_literal: true -require 'rails_helper' +require "rails_helper" RSpec.describe DiscourseChatIntegration::Provider::GroupmeProvider do let(:post) { Fabricate(:post) } - describe '.trigger_notifications' do + describe ".trigger_notifications" do before do SiteSetting.chat_integration_groupme_enabled = true - SiteSetting.chat_integration_groupme_bot_ids = '1a2b3c4d5e6f7g' + SiteSetting.chat_integration_groupme_bot_ids = "1a2b3c4d5e6f7g" end - let(:chan1) { DiscourseChatIntegration::Channel.create!(provider: 'groupme', data: { groupme_bot_id: '1a2b3c4d5e6f7g' }) } + let(:chan1) do + DiscourseChatIntegration::Channel.create!( + provider: "groupme", + data: { + groupme_bot_id: "1a2b3c4d5e6f7g", + }, + ) + end - it 'sends a request' do + it "sends a request" do stub1 = stub_request(:post, "https://api.groupme.com/v3/bots/post").to_return(status: 200) described_class.trigger_notification(post, chan1, nil) expect(stub1).to have_been_requested.once end - it 'handles errors correctly' do - stub1 = stub_request(:post, "https://api.groupme.com/v3/bots/post").to_return(status: 404, body: "{ \"error\": \"Not Found\"}") + it "handles errors correctly" do + stub1 = + stub_request(:post, "https://api.groupme.com/v3/bots/post").to_return( + status: 404, + body: "{ \"error\": \"Not Found\"}", + ) expect(stub1).to have_been_requested.times(0) - expect { described_class.trigger_notification(post, chan1, nil) }.to raise_exception(::DiscourseChatIntegration::ProviderError) + expect { described_class.trigger_notification(post, chan1, nil) }.to raise_exception( + ::DiscourseChatIntegration::ProviderError, + ) expect(stub1).to have_been_requested.once end end diff --git a/spec/lib/discourse_chat_integration/provider/guilded/guilded_provider_spec.rb b/spec/lib/discourse_chat_integration/provider/guilded/guilded_provider_spec.rb index 98ab3e2..5a1a2fd 100644 --- a/spec/lib/discourse_chat_integration/provider/guilded/guilded_provider_spec.rb +++ b/spec/lib/discourse_chat_integration/provider/guilded/guilded_provider_spec.rb @@ -1,30 +1,38 @@ # frozen_string_literal: true -require 'rails_helper' +require "rails_helper" RSpec.describe DiscourseChatIntegration::Provider::GuildedProvider do let(:post) { Fabricate(:post) } - describe '.trigger_notifications' do - before do - SiteSetting.chat_integration_guilded_enabled = true + describe ".trigger_notifications" do + before { SiteSetting.chat_integration_guilded_enabled = true } + + let(:chan1) do + DiscourseChatIntegration::Channel.create!( + provider: "guilded", + data: { + name: "Awesome Channel", + webhook_url: "https://media.guilded.gg/webhooks/1234/abcd", + }, + ) end - let(:chan1) { DiscourseChatIntegration::Channel.create!(provider: 'guilded', data: { name: "Awesome Channel", webhook_url: 'https://media.guilded.gg/webhooks/1234/abcd' }) } - - it 'sends a webhook request' do - stub1 = stub_request(:post, 'https://media.guilded.gg/webhooks/1234/abcd').to_return(status: 200) + it "sends a webhook request" do + stub1 = + stub_request(:post, "https://media.guilded.gg/webhooks/1234/abcd").to_return(status: 200) described_class.trigger_notification(post, chan1, nil) expect(stub1).to have_been_requested.once end - it 'handles errors correctly' do - stub1 = stub_request(:post, "https://media.guilded.gg/webhooks/1234/abcd").to_return(status: 400) + it "handles errors correctly" do + stub1 = + stub_request(:post, "https://media.guilded.gg/webhooks/1234/abcd").to_return(status: 400) expect(stub1).to have_been_requested.times(0) - expect { described_class.trigger_notification(post, chan1, nil) }.to raise_exception(::DiscourseChatIntegration::ProviderError) + expect { described_class.trigger_notification(post, chan1, nil) }.to raise_exception( + ::DiscourseChatIntegration::ProviderError, + ) expect(stub1).to have_been_requested.once end - end - end diff --git a/spec/lib/discourse_chat_integration/provider/matrix/matrix_provider_spec.rb b/spec/lib/discourse_chat_integration/provider/matrix/matrix_provider_spec.rb index 0a05288..0733a66 100644 --- a/spec/lib/discourse_chat_integration/provider/matrix/matrix_provider_spec.rb +++ b/spec/lib/discourse_chat_integration/provider/matrix/matrix_provider_spec.rb @@ -1,31 +1,47 @@ # frozen_string_literal: true -require 'rails_helper' +require "rails_helper" RSpec.describe DiscourseChatIntegration::Provider::MatrixProvider do let(:post) { Fabricate(:post) } - describe '.trigger_notifications' do + describe ".trigger_notifications" do before do SiteSetting.chat_integration_matrix_enabled = true - SiteSetting.chat_integration_matrix_access_token = 'abcd' + SiteSetting.chat_integration_matrix_access_token = "abcd" end - let(:chan1) { DiscourseChatIntegration::Channel.create!(provider: 'matrix', data: { name: "Awesome Channel", room_id: '!blah:matrix.org' }) } + let(:chan1) do + DiscourseChatIntegration::Channel.create!( + provider: "matrix", + data: { + name: "Awesome Channel", + room_id: "!blah:matrix.org", + }, + ) + end - it 'sends the message' do - stub1 = stub_request(:put, %r{https://matrix.org/_matrix/client/r0/rooms/!blah:matrix.org/send/m.room.message/*}).to_return(status: 200) + it "sends the message" do + stub1 = + stub_request( + :put, + %r{https://matrix.org/_matrix/client/r0/rooms/!blah:matrix.org/send/m.room.message/*}, + ).to_return(status: 200) described_class.trigger_notification(post, chan1, nil) expect(stub1).to have_been_requested.once end - it 'handles errors correctly' do - stub1 = stub_request(:put, %r{https://matrix.org/_matrix/client/r0/rooms/!blah:matrix.org/send/m.room.message/*}).to_return(status: 400, body: '{"errmsg":"M_UNKNOWN"}') + it "handles errors correctly" do + stub1 = + stub_request( + :put, + %r{https://matrix.org/_matrix/client/r0/rooms/!blah:matrix.org/send/m.room.message/*}, + ).to_return(status: 400, body: '{"errmsg":"M_UNKNOWN"}') expect(stub1).to have_been_requested.times(0) - expect { described_class.trigger_notification(post, chan1, nil) }.to raise_exception(::DiscourseChatIntegration::ProviderError) + expect { described_class.trigger_notification(post, chan1, nil) }.to raise_exception( + ::DiscourseChatIntegration::ProviderError, + ) expect(stub1).to have_been_requested.once end - end - end diff --git a/spec/lib/discourse_chat_integration/provider/mattermost/mattermost_command_controller_spec.rb b/spec/lib/discourse_chat_integration/provider/mattermost/mattermost_command_controller_spec.rb index 06402fc..8ef25b3 100644 --- a/spec/lib/discourse_chat_integration/provider/mattermost/mattermost_command_controller_spec.rb +++ b/spec/lib/discourse_chat_integration/provider/mattermost/mattermost_command_controller_spec.rb @@ -1,88 +1,94 @@ # frozen_string_literal: true -require 'rails_helper' +require "rails_helper" -describe 'Mattermost Command Controller', type: :request do +describe "Mattermost Command Controller", type: :request do let(:category) { Fabricate(:category) } let(:tag) { Fabricate(:tag) } let(:tag2) { Fabricate(:tag) } - let!(:chan1) { DiscourseChatIntegration::Channel.create!(provider: 'mattermost', data: { identifier: '#welcome' }) } + let!(:chan1) do + DiscourseChatIntegration::Channel.create!( + provider: "mattermost", + data: { + identifier: "#welcome", + }, + ) + end - describe 'with plugin disabled' do - it 'should return a 404' do - post '/chat-integration/mattermost/command.json' + describe "with plugin disabled" do + it "should return a 404" do + post "/chat-integration/mattermost/command.json" expect(response.status).to eq(404) end end - describe 'with plugin enabled and provider disabled' do + describe "with plugin enabled and provider disabled" do before do SiteSetting.chat_integration_enabled = true SiteSetting.chat_integration_mattermost_enabled = false end - it 'should return a 404' do - post '/chat-integration/mattermost/command.json' + it "should return a 404" do + post "/chat-integration/mattermost/command.json" expect(response.status).to eq(404) end end - describe 'slash commands endpoint' do + describe "slash commands endpoint" do before do SiteSetting.chat_integration_enabled = true - SiteSetting.chat_integration_mattermost_webhook_url = "https://hooks.mattermost.com/services/abcde" + SiteSetting.chat_integration_mattermost_webhook_url = + "https://hooks.mattermost.com/services/abcde" SiteSetting.chat_integration_mattermost_enabled = true end - describe 'when forum is private' do - it 'should not redirect to login page' do + describe "when forum is private" do + it "should not redirect to login page" do SiteSetting.login_required = true - token = 'sometoken' + token = "sometoken" SiteSetting.chat_integration_mattermost_incoming_webhook_token = token - post '/chat-integration/mattermost/command.json', params: { - text: 'help', token: token - } + post "/chat-integration/mattermost/command.json", params: { text: "help", token: token } expect(response.status).to eq(200) end end - describe 'when the token is invalid' do - it 'should raise the right error' do - post '/chat-integration/mattermost/command.json', params: { text: 'help' } + describe "when the token is invalid" do + it "should raise the right error" do + post "/chat-integration/mattermost/command.json", params: { text: "help" } expect(response.status).to eq(400) end end - describe 'when incoming webhook token has not been set' do - it 'should raise the right error' do - post '/chat-integration/mattermost/command.json', params: { - text: 'help', token: 'some token' - } + describe "when incoming webhook token has not been set" do + it "should raise the right error" do + post "/chat-integration/mattermost/command.json", + params: { + text: "help", + token: "some token", + } expect(response.status).to eq(403) end end - describe 'when token is valid' do + describe "when token is valid" do let(:token) { "Secret Sauce" } # No need to test every single command here, that's tested # by helper_spec upstream - before do - SiteSetting.chat_integration_mattermost_incoming_webhook_token = token - end + before { SiteSetting.chat_integration_mattermost_incoming_webhook_token = token } - describe 'add new rule' do - - it 'should add a new rule correctly' do - post "/chat-integration/mattermost/command.json", params: { - text: "watch #{category.slug}", - channel_name: 'welcome', - token: token - } + describe "add new rule" do + it "should add a new rule correctly" do + post "/chat-integration/mattermost/command.json", + params: { + text: "watch #{category.slug}", + channel_name: "welcome", + token: token, + } json = response.parsed_body @@ -90,34 +96,40 @@ describe 'Mattermost Command Controller', type: :request do rule = DiscourseChatIntegration::Rule.all.first expect(rule.channel).to eq(chan1) - expect(rule.filter).to eq('watch') + expect(rule.filter).to eq("watch") expect(rule.category_id).to eq(category.id) expect(rule.tags).to eq(nil) end - describe 'from an unknown channel' do - it 'creates the channel' do - post "/chat-integration/mattermost/command.json", params: { - text: "watch #{category.slug}", - channel_name: 'general', - token: token - } + describe "from an unknown channel" do + it "creates the channel" do + post "/chat-integration/mattermost/command.json", + params: { + text: "watch #{category.slug}", + channel_name: "general", + token: token, + } json = response.parsed_body - expect(json["text"]).to eq(I18n.t("chat_integration.provider.mattermost.create.created")) + expect(json["text"]).to eq( + I18n.t("chat_integration.provider.mattermost.create.created"), + ) - chan = DiscourseChatIntegration::Channel.with_provider('mattermost').with_data_value('identifier', '#general').first - expect(chan.provider).to eq('mattermost') + chan = + DiscourseChatIntegration::Channel + .with_provider("mattermost") + .with_data_value("identifier", "#general") + .first + expect(chan.provider).to eq("mattermost") rule = chan.rules.first - expect(rule.filter).to eq('watch') + expect(rule.filter).to eq("watch") expect(rule.category_id).to eq(category.id) expect(rule.tags).to eq(nil) end end end - end end end diff --git a/spec/lib/discourse_chat_integration/provider/mattermost/mattermost_provider_spec.rb b/spec/lib/discourse_chat_integration/provider/mattermost/mattermost_provider_spec.rb index 5797e5c..855fd02 100644 --- a/spec/lib/discourse_chat_integration/provider/mattermost/mattermost_provider_spec.rb +++ b/spec/lib/discourse_chat_integration/provider/mattermost/mattermost_provider_spec.rb @@ -1,11 +1,11 @@ # frozen_string_literal: true -require 'rails_helper' +require "rails_helper" RSpec.describe DiscourseChatIntegration::Provider::MattermostProvider do let(:post) { Fabricate(:post) } - describe '.trigger_notifications' do + describe ".trigger_notifications" do let(:upload) { Fabricate(:upload) } before do @@ -14,36 +14,47 @@ RSpec.describe DiscourseChatIntegration::Provider::MattermostProvider do SiteSetting.logo_small = upload end - let(:chan1) { DiscourseChatIntegration::Channel.create!(provider: 'mattermost', data: { identifier: "#awesomechannel" }) } + let(:chan1) do + DiscourseChatIntegration::Channel.create!( + provider: "mattermost", + data: { + identifier: "#awesomechannel", + }, + ) + end - it 'sends a webhook request' do - stub1 = stub_request(:post, 'https://mattermost.blah/hook/abcd').to_return(status: 200) + it "sends a webhook request" do + stub1 = stub_request(:post, "https://mattermost.blah/hook/abcd").to_return(status: 200) described_class.trigger_notification(post, chan1, nil) expect(stub1).to have_been_requested.once end - describe 'when mattermost icon is not configured' do - it 'defaults to the right icon' do + describe "when mattermost icon is not configured" do + it "defaults to the right icon" do message = described_class.mattermost_message(post, chan1) expect(message[:icon_url]).to eq(UrlHelper.absolute(upload.url)) end end - describe 'when mattermost icon has been configured' do - it 'should use the right icon' do + describe "when mattermost icon has been configured" do + it "should use the right icon" do SiteSetting.chat_integration_mattermost_icon_url = "https://specific_logo" message = described_class.mattermost_message(post, chan1) expect(message[:icon_url]).to eq(SiteSetting.chat_integration_mattermost_icon_url) end end - it 'handles errors correctly' do - stub1 = stub_request(:post, "https://mattermost.blah/hook/abcd").to_return(status: 500, body: "error") + it "handles errors correctly" do + stub1 = + stub_request(:post, "https://mattermost.blah/hook/abcd").to_return( + status: 500, + body: "error", + ) expect(stub1).to have_been_requested.times(0) - expect { described_class.trigger_notification(post, chan1, nil) }.to raise_exception(::DiscourseChatIntegration::ProviderError) + expect { described_class.trigger_notification(post, chan1, nil) }.to raise_exception( + ::DiscourseChatIntegration::ProviderError, + ) expect(stub1).to have_been_requested.once end - end - end diff --git a/spec/lib/discourse_chat_integration/provider/rocketchat/rocketchat_provider_spec.rb b/spec/lib/discourse_chat_integration/provider/rocketchat/rocketchat_provider_spec.rb index 7f32fbf..4652172 100644 --- a/spec/lib/discourse_chat_integration/provider/rocketchat/rocketchat_provider_spec.rb +++ b/spec/lib/discourse_chat_integration/provider/rocketchat/rocketchat_provider_spec.rb @@ -1,31 +1,38 @@ # frozen_string_literal: true -require 'rails_helper' +require "rails_helper" RSpec.describe DiscourseChatIntegration::Provider::RocketchatProvider do let(:post) { Fabricate(:post) } - describe '.trigger_notifications' do + describe ".trigger_notifications" do before do SiteSetting.chat_integration_rocketchat_enabled = true SiteSetting.chat_integration_rocketchat_webhook_url = "https://example.com/abcd" end - let(:chan1) { DiscourseChatIntegration::Channel.create!(provider: 'rocketchat', data: { identifier: "#general" }) } + let(:chan1) do + DiscourseChatIntegration::Channel.create!( + provider: "rocketchat", + data: { + identifier: "#general", + }, + ) + end - it 'sends a webhook request' do - stub1 = stub_request(:post, 'https://example.com/abcd').to_return(body: "{\"success\":true}") + it "sends a webhook request" do + stub1 = stub_request(:post, "https://example.com/abcd").to_return(body: "{\"success\":true}") described_class.trigger_notification(post, chan1, nil) expect(stub1).to have_been_requested.once end - it 'handles errors correctly' do - stub1 = stub_request(:post, 'https://example.com/abcd').to_return(status: 400, body: "{}") + it "handles errors correctly" do + stub1 = stub_request(:post, "https://example.com/abcd").to_return(status: 400, body: "{}") expect(stub1).to have_been_requested.times(0) - expect { described_class.trigger_notification(post, chan1, nil) }.to raise_exception(::DiscourseChatIntegration::ProviderError) + expect { described_class.trigger_notification(post, chan1, nil) }.to raise_exception( + ::DiscourseChatIntegration::ProviderError, + ) expect(stub1).to have_been_requested.once end - end - end diff --git a/spec/lib/discourse_chat_integration/provider/slack/slack_command_controller_spec.rb b/spec/lib/discourse_chat_integration/provider/slack/slack_command_controller_spec.rb index 4bfa178..18ddec6 100644 --- a/spec/lib/discourse_chat_integration/provider/slack/slack_command_controller_spec.rb +++ b/spec/lib/discourse_chat_integration/provider/slack/slack_command_controller_spec.rb @@ -1,106 +1,99 @@ # frozen_string_literal: true -require 'rails_helper' +require "rails_helper" -describe 'Slack Command Controller', type: :request do - before do - Discourse.cache.clear - end +describe "Slack Command Controller", type: :request do + before { Discourse.cache.clear } let(:category) { Fabricate(:category) } let(:tag) { Fabricate(:tag) } let(:tag2) { Fabricate(:tag) } - let!(:chan1) { DiscourseChatIntegration::Channel.create!(provider: 'slack', data: { identifier: '#welcome' }) } + let!(:chan1) do + DiscourseChatIntegration::Channel.create!(provider: "slack", data: { identifier: "#welcome" }) + end - describe 'with plugin disabled' do - it 'should return a 404' do - post '/chat-integration/slack/command.json' + describe "with plugin disabled" do + it "should return a 404" do + post "/chat-integration/slack/command.json" expect(response.status).to eq(404) end end - describe 'with plugin enabled and provider disabled' do + describe "with plugin enabled and provider disabled" do before do SiteSetting.chat_integration_enabled = true SiteSetting.chat_integration_slack_enabled = false end - it 'should return a 404' do - post '/chat-integration/slack/command.json' + it "should return a 404" do + post "/chat-integration/slack/command.json" expect(response.status).to eq(404) end end - describe 'slash commands endpoint' do + describe "slash commands endpoint" do before do SiteSetting.chat_integration_enabled = true - SiteSetting.chat_integration_slack_outbound_webhook_url = "https://hooks.slack.com/services/abcde" + SiteSetting.chat_integration_slack_outbound_webhook_url = + "https://hooks.slack.com/services/abcde" SiteSetting.chat_integration_slack_enabled = true end - describe 'when forum is private' do - it 'should not redirect to login page' do + describe "when forum is private" do + it "should not redirect to login page" do SiteSetting.login_required = true - token = 'sometoken' + token = "sometoken" SiteSetting.chat_integration_slack_incoming_webhook_token = token - post '/chat-integration/slack/command.json', params: { - text: 'help', token: token - } + post "/chat-integration/slack/command.json", params: { text: "help", token: token } expect(response.status).to eq(200) end end - describe 'when the token is invalid' do - it 'should raise the right error' do - post '/chat-integration/slack/command.json', params: { text: 'help' } + describe "when the token is invalid" do + it "should raise the right error" do + post "/chat-integration/slack/command.json", params: { text: "help" } expect(response.status).to eq(400) end end - describe 'backwards compatibility with discourse-slack-official' do - it 'should return the right response' do - token = 'secret sauce' + describe "backwards compatibility with discourse-slack-official" do + it "should return the right response" do + token = "secret sauce" SiteSetting.chat_integration_slack_incoming_webhook_token = token - post '/slack/command.json', params: { - text: 'help', token: token - } + post "/slack/command.json", params: { text: "help", token: token } expect(response.status).to eq(200) expect(response.parsed_body["text"]).to be_present end end - describe 'when incoming webhook token has not been set' do - it 'should raise the right error' do - post '/chat-integration/slack/command.json', params: { - text: 'help', token: 'some token' - } + describe "when incoming webhook token has not been set" do + it "should raise the right error" do + post "/chat-integration/slack/command.json", params: { text: "help", token: "some token" } expect(response.status).to eq(403) end end - describe 'when token is valid' do + describe "when token is valid" do let(:token) { "Secret Sauce" } # No need to test every single command here, that's tested # by helper_spec upstream - before do - SiteSetting.chat_integration_slack_incoming_webhook_token = token - end + before { SiteSetting.chat_integration_slack_incoming_webhook_token = token } - describe 'add new rule' do - - it 'should add a new rule correctly' do - post "/chat-integration/slack/command.json", params: { - text: "watch #{category.slug}", - channel_name: 'welcome', - token: token - } + describe "add new rule" do + it "should add a new rule correctly" do + post "/chat-integration/slack/command.json", + params: { + text: "watch #{category.slug}", + channel_name: "welcome", + token: token, + } json = response.parsed_body @@ -108,277 +101,314 @@ describe 'Slack Command Controller', type: :request do rule = DiscourseChatIntegration::Rule.all.first expect(rule.channel).to eq(chan1) - expect(rule.filter).to eq('watch') + expect(rule.filter).to eq("watch") expect(rule.category_id).to eq(category.id) expect(rule.tags).to eq(nil) end - describe 'from an unknown channel' do - it 'creates the channel' do - post "/chat-integration/slack/command.json", params: { - text: "watch #{category.slug}", - channel_name: 'general', - token: token - } + describe "from an unknown channel" do + it "creates the channel" do + post "/chat-integration/slack/command.json", + params: { + text: "watch #{category.slug}", + channel_name: "general", + token: token, + } json = response.parsed_body expect(json["text"]).to eq(I18n.t("chat_integration.provider.slack.create.created")) - chan = DiscourseChatIntegration::Channel.with_provider('slack').with_data_value('identifier', '#general').first - expect(chan.provider).to eq('slack') + chan = + DiscourseChatIntegration::Channel + .with_provider("slack") + .with_data_value("identifier", "#general") + .first + expect(chan.provider).to eq("slack") rule = chan.rules.first - expect(rule.filter).to eq('watch') + expect(rule.filter).to eq("watch") expect(rule.category_id).to eq(category.id) expect(rule.tags).to eq(nil) end end end - describe 'post transcript' do - let(:messages_fixture) { + describe "post transcript" do + let(:messages_fixture) do [ { - "type": "message", - "user": "U6JSSESES", - "text": "Yeah, should make posting slack transcripts much easier", - "ts": "1501801665.062694" + type: "message", + user: "U6JSSESES", + text: "Yeah, should make posting slack transcripts much easier", + ts: "1501801665.062694", }, { - "type": "message", - "user": "U5Z773QLS", - "text": "Oooh a new discourse plugin???", - "ts": "1501801643.056375" + type: "message", + user: "U5Z773QLS", + text: "Oooh a new discourse plugin???", + ts: "1501801643.056375", + }, + { type: "message", user: "U6E2W7R8C", text: "Which one?", ts: "1501801634.053761" }, + { + type: "message", + user: "U6JSSESES", + text: + "So, who's interested in the new ?", + ts: "1501801629.052212", }, { - "type": "message", - "user": "U6E2W7R8C", - "text": "Which one?", - "ts": "1501801634.053761" + text: "", + username: "Test Community", + bot_id: "B6C6JNUDN", + attachments: [ + { + author_name: "@david", + fallback: "Discourse can now be integrated with Mattermost! - @david", + text: "Hey , what do you think about this?", + title: "Discourse can now be integrated with Mattermost! [Announcements] ", + id: 1, + title_link: + "http://localhost:3000/t/discourse-can-now-be-integrated-with-mattermost/51/4", + color: "283890", + mrkdwn_in: ["text"], + }, + ], + type: "message", + subtype: "bot_message", + ts: "1501615820.949638", }, { - "type": "message", - "user": "U6JSSESES", - "text": "So, who's interested in the new ?", - "ts": "1501801629.052212" + type: "message", + user: "U5Z773QLS", + text: "Let’s try some *bold text*", + ts: "1501093331.439776", }, - { - "text": "", - "username": "Test Community", - "bot_id": "B6C6JNUDN", - "attachments": [ - { - "author_name": "@david", - "fallback": "Discourse can now be integrated with Mattermost! - @david", - "text": "Hey , what do you think about this?", - "title": "Discourse can now be integrated with Mattermost! [Announcements] ", - "id": 1, - "title_link": "http://localhost:3000/t/discourse-can-now-be-integrated-with-mattermost/51/4", - "color": "283890", - "mrkdwn_in": [ - "text" - ] - } - ], - "type": "message", - "subtype": "bot_message", - "ts": "1501615820.949638" - }, - { - "type": "message", - "user": "U5Z773QLS", - "text": "Let’s try some *bold text*", - "ts": "1501093331.439776" - }, - ] - } - - before do - SiteSetting.chat_integration_slack_access_token = 'abcde' end + before { SiteSetting.chat_integration_slack_access_token = "abcde" } + context "with valid slack responses" do before do - stub_request(:post, "https://slack.com/api/users.list").to_return(body: '{"ok":true,"members":[{"id":"U5Z773QLS","profile":{"display_name":"david","real_name":"david","icon_24":"https://example.com/avatar"}}],"response_metadata":{"next_cursor":""}}') - stub_request(:post, "https://slack.com/api/conversations.history").to_return(body: { ok: true, messages: messages_fixture }.to_json) + stub_request(:post, "https://slack.com/api/users.list").to_return( + body: + '{"ok":true,"members":[{"id":"U5Z773QLS","profile":{"display_name":"david","real_name":"david","icon_24":"https://example.com/avatar"}}],"response_metadata":{"next_cursor":""}}', + ) + stub_request(:post, "https://slack.com/api/conversations.history").to_return( + body: { ok: true, messages: messages_fixture }.to_json, + ) end - it 'generates the transcript UI properly' do - command_stub = stub_request(:post, "https://slack.com/commands/1234") - .with(body: /attachments/) - .to_return(body: { ok: true }.to_json) + it "generates the transcript UI properly" do + command_stub = + stub_request(:post, "https://slack.com/commands/1234").with( + body: /attachments/, + ).to_return(body: { ok: true }.to_json) - post "/chat-integration/slack/command.json", params: { - text: "post", - response_url: 'https://hooks.slack.com/commands/1234', - channel_name: 'general', - channel_id: 'C6029G78F', - token: token - } + post "/chat-integration/slack/command.json", + params: { + text: "post", + response_url: "https://hooks.slack.com/commands/1234", + channel_name: "general", + channel_id: "C6029G78F", + token: token, + } expect(command_stub).to have_been_requested end - it 'can select by url' do - command_stub = stub_request(:post, "https://slack.com/commands/1234") - .with(body: /1501801629\.052212/) - .to_return(body: { ok: true }.to_json) + it "can select by url" do + command_stub = + stub_request(:post, "https://slack.com/commands/1234").with( + body: /1501801629\.052212/, + ).to_return(body: { ok: true }.to_json) - post "/chat-integration/slack/command.json", params: { - text: "post https://sometestslack.slack.com/archives/C6029G78F/p1501801629052212", - response_url: 'https://hooks.slack.com/commands/1234', - channel_name: 'general', - channel_id: 'C6029G78F', - token: token - } + post "/chat-integration/slack/command.json", + params: { + text: + "post https://sometestslack.slack.com/archives/C6029G78F/p1501801629052212", + response_url: "https://hooks.slack.com/commands/1234", + channel_name: "general", + channel_id: "C6029G78F", + token: token, + } expect(command_stub).to have_been_requested end - it 'can select by url with thread parameter' do - replies_stub = stub_request(:post, "https://slack.com/api/conversations.replies") - .with(body: /1501801629\.052212/) - .to_return(body: { ok: true, messages: messages_fixture }.to_json) + it "can select by url with thread parameter" do + replies_stub = + stub_request(:post, "https://slack.com/api/conversations.replies").with( + body: /1501801629\.052212/, + ).to_return(body: { ok: true, messages: messages_fixture }.to_json) - command_stub = stub_request(:post, "https://slack.com/commands/1234") - .to_return(body: { ok: true }.to_json) + command_stub = + stub_request(:post, "https://slack.com/commands/1234").to_return( + body: { ok: true }.to_json, + ) - post "/chat-integration/slack/command.json", params: { - text: "post https://sometestslack.slack.com/archives/C6029G78F/p1501201669054212?thread_ts=1501801629.052212", - response_url: 'https://hooks.slack.com/commands/1234', - channel_name: 'general', - channel_id: 'C6029G78F', - token: token - } + post "/chat-integration/slack/command.json", + params: { + text: + "post https://sometestslack.slack.com/archives/C6029G78F/p1501201669054212?thread_ts=1501801629.052212", + response_url: "https://hooks.slack.com/commands/1234", + channel_name: "general", + channel_id: "C6029G78F", + token: token, + } expect(command_stub).to have_been_requested expect(replies_stub).to have_been_requested end - it 'can select by thread' do - replies_stub = stub_request(:post, "https://slack.com/api/conversations.replies") - .with(body: /1501801629\.052212/) - .to_return(body: { ok: true, messages: messages_fixture }.to_json) + it "can select by thread" do + replies_stub = + stub_request(:post, "https://slack.com/api/conversations.replies").with( + body: /1501801629\.052212/, + ).to_return(body: { ok: true, messages: messages_fixture }.to_json) - command_stub = stub_request(:post, "https://slack.com/commands/1234") - .to_return(body: { ok: true }.to_json) + command_stub = + stub_request(:post, "https://slack.com/commands/1234").to_return( + body: { ok: true }.to_json, + ) - post "/chat-integration/slack/command.json", params: { - text: "post thread https://sometestslack.slack.com/archives/C6029G78F/p1501801629052212", - response_url: 'https://hooks.slack.com/commands/1234', - channel_name: 'general', - channel_id: 'C6029G78F', - token: token - } + post "/chat-integration/slack/command.json", + params: { + text: + "post thread https://sometestslack.slack.com/archives/C6029G78F/p1501801629052212", + response_url: "https://hooks.slack.com/commands/1234", + channel_name: "general", + channel_id: "C6029G78F", + token: token, + } expect(command_stub).to have_been_requested expect(replies_stub).to have_been_requested end - it 'can select by count' do - command_stub = stub_request(:post, "https://slack.com/commands/1234") - .with(body: /1501801629\.052212/) - .to_return(body: { ok: true }.to_json) + it "can select by count" do + command_stub = + stub_request(:post, "https://slack.com/commands/1234").with( + body: /1501801629\.052212/, + ).to_return(body: { ok: true }.to_json) - post "/chat-integration/slack/command.json", params: { - text: "post 4", - response_url: 'https://hooks.slack.com/commands/1234', - channel_name: 'general', - channel_id: 'C6029G78F', - token: token - } + post "/chat-integration/slack/command.json", + params: { + text: "post 4", + response_url: "https://hooks.slack.com/commands/1234", + channel_name: "general", + channel_id: "C6029G78F", + token: token, + } expect(command_stub).to have_been_requested end - it 'can auto select' do - command_stub = stub_request(:post, "https://slack.com/commands/1234") - .with(body: /1501615820\.949638/) - .to_return(body: { ok: true }.to_json) + it "can auto select" do + command_stub = + stub_request(:post, "https://slack.com/commands/1234").with( + body: /1501615820\.949638/, + ).to_return(body: { ok: true }.to_json) - post "/chat-integration/slack/command.json", params: { - text: "post", - response_url: 'https://hooks.slack.com/commands/1234', - channel_name: 'general', - channel_id: 'C6029G78F', - token: token - } + post "/chat-integration/slack/command.json", + params: { + text: "post", + response_url: "https://hooks.slack.com/commands/1234", + channel_name: "general", + channel_id: "C6029G78F", + token: token, + } expect(command_stub).to have_been_requested end it "supports using shortcuts to create a thread transcript" do - replies_stub = stub_request(:post, "https://slack.com/api/conversations.replies") - .with(body: /1501801629\.052212/) - .to_return(body: { ok: true, messages: messages_fixture }.to_json) + replies_stub = + stub_request(:post, "https://slack.com/api/conversations.replies").with( + body: /1501801629\.052212/, + ).to_return(body: { ok: true, messages: messages_fixture }.to_json) - view_open_stub = stub_request(:post, "https://slack.com/api/views.open") - .with(body: /TRIGGERID/) - .to_return(body: { ok: true, view: { id: "VIEWID" } }.to_json) + view_open_stub = + stub_request(:post, "https://slack.com/api/views.open").with( + body: /TRIGGERID/, + ).to_return(body: { ok: true, view: { id: "VIEWID" } }.to_json) - view_update_stub = stub_request(:post, "https://slack.com/api/views.update") - .with(body: /VIEWID/) - .to_return(body: { ok: true }.to_json) + view_update_stub = + stub_request(:post, "https://slack.com/api/views.update").with( + body: /VIEWID/, + ).to_return(body: { ok: true }.to_json) - post "/chat-integration/slack/interactive.json", params: { - payload: { - type: "message_action", - channel: { name: 'general', id: 'C6029G78F' }, - trigger_id: "TRIGGERID", - message: { thread_ts: "1501801629.052212" }, - token: token - }.to_json - } + post "/chat-integration/slack/interactive.json", + params: { + payload: { + type: "message_action", + channel: { + name: "general", + id: "C6029G78F", + }, + trigger_id: "TRIGGERID", + message: { + thread_ts: "1501801629.052212", + }, + token: token, + }.to_json, + } expect(response.status).to eq(200) expect(view_open_stub).to have_been_requested expect(view_update_stub).to have_been_requested end - end - it 'deals with failed API calls correctly' do - command_stub = stub_request(:post, "https://slack.com/commands/1234") - .with(body: { text: I18n.t("chat_integration.provider.slack.transcript.error_users") }) - .to_return(body: { ok: true }.to_json) + it "deals with failed API calls correctly" do + command_stub = + stub_request(:post, "https://slack.com/commands/1234").with( + body: { + text: I18n.t("chat_integration.provider.slack.transcript.error_users"), + }, + ).to_return(body: { ok: true }.to_json) stub_request(:post, "https://slack.com/api/users.list").to_return(status: 403) - post "/chat-integration/slack/command.json", params: { - text: "post 2", - response_url: 'https://hooks.slack.com/commands/1234', - channel_name: 'general', - channel_id: 'C6029G78F', - token: token - } + post "/chat-integration/slack/command.json", + params: { + text: "post 2", + response_url: "https://hooks.slack.com/commands/1234", + channel_name: "general", + channel_id: "C6029G78F", + token: token, + } json = response.parsed_body - expect(json["text"]).to include(I18n.t("chat_integration.provider.slack.transcript.loading")) + expect(json["text"]).to include( + I18n.t("chat_integration.provider.slack.transcript.loading"), + ) expect(command_stub).to have_been_requested end - it 'errors correctly if there is no api key' do - SiteSetting.chat_integration_slack_access_token = '' + it "errors correctly if there is no api key" do + SiteSetting.chat_integration_slack_access_token = "" - post "/chat-integration/slack/command.json", params: { - text: "post 2", - response_url: 'https://hooks.slack.com/commands/1234', - channel_name: 'general', - channel_id: 'C6029G78F', - token: token - } + post "/chat-integration/slack/command.json", + params: { + text: "post 2", + response_url: "https://hooks.slack.com/commands/1234", + channel_name: "general", + channel_id: "C6029G78F", + token: token, + } json = response.parsed_body - expect(json["text"]).to include(I18n.t("chat_integration.provider.slack.transcript.api_required")) + expect(json["text"]).to include( + I18n.t("chat_integration.provider.slack.transcript.api_required"), + ) end end - end end end diff --git a/spec/lib/discourse_chat_integration/provider/slack/slack_message_formatter_spec.rb b/spec/lib/discourse_chat_integration/provider/slack/slack_message_formatter_spec.rb index 9404400..755449a 100644 --- a/spec/lib/discourse_chat_integration/provider/slack/slack_message_formatter_spec.rb +++ b/spec/lib/discourse_chat_integration/provider/slack/slack_message_formatter_spec.rb @@ -1,31 +1,33 @@ # frozen_string_literal: true -require 'rails_helper' +require "rails_helper" RSpec.describe DiscourseChatIntegration::Provider::SlackProvider::SlackMessageFormatter do - describe '.format' do - context 'with links' do - it 'should return the right message' do - expect(described_class.format("test")) - .to eq('') + describe ".format" do + context "with links" do + it "should return the right message" do + expect(described_class.format("test")).to eq( + "", + ) end - describe 'when text contains a link with an incomplete URL' do - it 'should return the right message' do - expect(described_class.format("test ")) - .to eq("test ") + describe "when text contains a link with an incomplete URL" do + it "should return the right message" do + expect(described_class.format("test ")).to eq( + "test ", + ) SiteSetting.force_https = true - expect(described_class.format("test ")) - .to eq("test ") + expect(described_class.format("test ")).to eq( + "test ", + ) end end it "should not raise an error with unparseable urls" do expect(described_class.format("test")).to eq("") end - end end end diff --git a/spec/lib/discourse_chat_integration/provider/slack/slack_provider_spec.rb b/spec/lib/discourse_chat_integration/provider/slack/slack_provider_spec.rb index 2a3c425..856c703 100644 --- a/spec/lib/discourse_chat_integration/provider/slack/slack_provider_spec.rb +++ b/spec/lib/discourse_chat_integration/provider/slack/slack_provider_spec.rb @@ -1,24 +1,22 @@ # frozen_string_literal: true -require 'rails_helper' +require "rails_helper" RSpec.describe DiscourseChatIntegration::Provider::SlackProvider do let(:post) { Fabricate(:post) } - describe '.excerpt' do - describe 'when post contains emoijs' do - before do - post.update!(raw: ':slight_smile: This is a test') - end + describe ".excerpt" do + describe "when post contains emoijs" do + before { post.update!(raw: ":slight_smile: This is a test") } - it 'should return the right excerpt' do - expect(described_class.excerpt(post)).to eq('🙂 This is a test') + it "should return the right excerpt" do + expect(described_class.excerpt(post)).to eq("🙂 This is a test") end end - describe 'when post contains onebox' do - it 'should return the right excerpt' do - post.update!(cooked: <<~COOKED + describe "when post contains onebox" do + it "should return the right excerpt" do + post.update!(cooked: <<~COOKED) COOKED - ) - expect(described_class.excerpt(post)) - .to eq('') + expect(described_class.excerpt(post)).to eq("") end end - describe 'when post contains an email' do - it 'should return the right excerpt' do - post.update!(cooked: <<~COOKED + describe "when post contains an email" do + it "should return the right excerpt" do + post.update!(cooked: <<~COOKED) The address is my email COOKED - ) - expect(described_class.excerpt(post)) - .to eq('The address is ') + expect(described_class.excerpt(post)).to eq( + "The address is ", + ) end end end - describe '.trigger_notifications' do + describe ".trigger_notifications" do before do - SiteSetting.chat_integration_slack_outbound_webhook_url = "https://hooks.slack.com/services/abcde" + SiteSetting.chat_integration_slack_outbound_webhook_url = + "https://hooks.slack.com/services/abcde" SiteSetting.chat_integration_slack_enabled = true end - let(:chan1) { DiscourseChatIntegration::Channel.create!(provider: 'slack', data: { identifier: '#general' }) } + let(:chan1) do + DiscourseChatIntegration::Channel.create!(provider: "slack", data: { identifier: "#general" }) + end - it 'sends a webhook request' do - stub1 = stub_request(:post, SiteSetting.chat_integration_slack_outbound_webhook_url).to_return(body: "success") + it "sends a webhook request" do + stub1 = + stub_request(:post, SiteSetting.chat_integration_slack_outbound_webhook_url).to_return( + body: "success", + ) described_class.trigger_notification(post, chan1, nil) expect(stub1).to have_been_requested.once end - it 'handles errors correctly' do - stub1 = stub_request(:post, SiteSetting.chat_integration_slack_outbound_webhook_url).to_return(status: 400, body: "error") + it "handles errors correctly" do + stub1 = + stub_request(:post, SiteSetting.chat_integration_slack_outbound_webhook_url).to_return( + status: 400, + body: "error", + ) expect(stub1).to have_been_requested.times(0) - expect { described_class.trigger_notification(post, chan1, nil) }.to raise_exception(::DiscourseChatIntegration::ProviderError) + expect { described_class.trigger_notification(post, chan1, nil) }.to raise_exception( + ::DiscourseChatIntegration::ProviderError, + ) expect(stub1).to have_been_requested.once end - describe 'with api token' do + describe "with api token" do before do SiteSetting.chat_integration_slack_access_token = "magic" @ts = "#{Time.now.to_i}.012345" @ts2 = "#{Time.now.to_i}.012346" - @stub1 = stub_request(:post, SiteSetting.chat_integration_slack_outbound_webhook_url).to_return(body: "success") - @stub2 = stub_request(:post, %r{https://slack.com/api/chat.postMessage}).to_return(body: "{\"ok\":true, \"ts\": \"#{@ts}\", \"message\": {\"attachments\": [], \"username\":\"blah\", \"text\":\"blah2\"} }", headers: { 'Content-Type' => 'application/json' }) - @thread_stub = stub_request(:post, %r{https://slack.com/api/chat.postMessage}).with(body: hash_including("thread_ts" => @ts)).to_return(body: "{\"ok\":true, \"ts\": \"#{@ts}\", \"message\": {\"attachments\": [], \"username\":\"blah\", \"text\":\"blah2\"} }", headers: { 'Content-Type' => 'application/json' }) - @thread_stub2 = stub_request(:post, %r{https://slack.com/api/chat.postMessage}).with(body: hash_including("thread_ts" => @ts2)).to_return(body: "{\"ok\":true, \"ts\": \"#{@ts2}\", \"message\": {\"attachments\": [], \"username\":\"blah\", \"text\":\"blah2\"} }", headers: { 'Content-Type' => 'application/json' }) + @stub1 = + stub_request(:post, SiteSetting.chat_integration_slack_outbound_webhook_url).to_return( + body: "success", + ) + @stub2 = + stub_request(:post, %r{https://slack.com/api/chat.postMessage}).to_return( + body: + "{\"ok\":true, \"ts\": \"#{@ts}\", \"message\": {\"attachments\": [], \"username\":\"blah\", \"text\":\"blah2\"} }", + headers: { + "Content-Type" => "application/json", + }, + ) + @thread_stub = + stub_request(:post, %r{https://slack.com/api/chat.postMessage}).with( + body: hash_including("thread_ts" => @ts), + ).to_return( + body: + "{\"ok\":true, \"ts\": \"#{@ts}\", \"message\": {\"attachments\": [], \"username\":\"blah\", \"text\":\"blah2\"} }", + headers: { + "Content-Type" => "application/json", + }, + ) + @thread_stub2 = + stub_request(:post, %r{https://slack.com/api/chat.postMessage}).with( + body: hash_including("thread_ts" => @ts2), + ).to_return( + body: + "{\"ok\":true, \"ts\": \"#{@ts2}\", \"message\": {\"attachments\": [], \"username\":\"blah\", \"text\":\"blah2\"} }", + headers: { + "Content-Type" => "application/json", + }, + ) end - it 'sends an api request' do + it "sends an api request" do expect(@stub2).to have_been_requested.times(0) expect(@thread_stub).to have_been_requested.times(0) @@ -108,7 +144,7 @@ RSpec.describe DiscourseChatIntegration::Provider::SlackProvider do expect(@thread_stub).to have_been_requested.times(0) end - it 'sends thread id for thread' do + it "sends thread id for thread" do expect(@thread_stub).to have_been_requested.times(0) rule = DiscourseChatIntegration::Rule.create(channel: chan1, filter: "thread") @@ -118,9 +154,15 @@ RSpec.describe DiscourseChatIntegration::Provider::SlackProvider do expect(@thread_stub).to have_been_requested.once end - it 'tracks threading in different channels separately' do + it "tracks threading in different channels separately" do expect(@thread_stub).to have_been_requested.times(0) - chan2 = DiscourseChatIntegration::Channel.create(provider: 'dummy2', data: { "identifier" => "#random" }) + chan2 = + DiscourseChatIntegration::Channel.create( + provider: "dummy2", + data: { + "identifier" => "#random", + }, + ) rule = DiscourseChatIntegration::Rule.create(channel: chan1, filter: "thread") rule2 = DiscourseChatIntegration::Rule.create(channel: chan2, filter: "thread") @@ -137,12 +179,11 @@ RSpec.describe DiscourseChatIntegration::Provider::SlackProvider do expect(described_class.get_slack_thread_ts(post.topic, "#random")).to eq(@ts2) end - it 'recognizes slack thread ts in comment' do - post.update!(cooked: "cooked", raw: <<~RAW + it "recognizes slack thread ts in comment" do + post.update!(cooked: "cooked", raw: <<~RAW) My fingers are typing words that improve `raw_quality` RAW - ) rule = DiscourseChatIntegration::Rule.create(channel: chan1, filter: "thread") @@ -152,14 +193,19 @@ RSpec.describe DiscourseChatIntegration::Provider::SlackProvider do expect(@thread_stub).to have_been_requested.times(1) end - it 'handles errors correctly' do - @stub2 = stub_request(:post, %r{https://slack.com/api/chat.postMessage}).to_return(body: "{\"ok\":false }", headers: { 'Content-Type' => 'application/json' }) - expect { described_class.trigger_notification(post, chan1, nil) }.to raise_exception(::DiscourseChatIntegration::ProviderError) + it "handles errors correctly" do + @stub2 = + stub_request(:post, %r{https://slack.com/api/chat.postMessage}).to_return( + body: "{\"ok\":false }", + headers: { + "Content-Type" => "application/json", + }, + ) + expect { described_class.trigger_notification(post, chan1, nil) }.to raise_exception( + ::DiscourseChatIntegration::ProviderError, + ) expect(@stub2).to have_been_requested.once end - end - end - end diff --git a/spec/lib/discourse_chat_integration/provider/slack/slack_transcript_spec.rb b/spec/lib/discourse_chat_integration/provider/slack/slack_transcript_spec.rb index 60309a2..62cbe1b 100644 --- a/spec/lib/discourse_chat_integration/provider/slack/slack_transcript_spec.rb +++ b/spec/lib/discourse_chat_integration/provider/slack/slack_transcript_spec.rb @@ -1,77 +1,69 @@ # frozen_string_literal: true -require 'rails_helper' +require "rails_helper" RSpec.describe DiscourseChatIntegration::Provider::SlackProvider::SlackTranscript do - before do - Discourse.cache.clear - end + before { Discourse.cache.clear } - let(:messages_fixture) { + let(:messages_fixture) do [ { - "type": "message", - "user": "U6JSSESES", - "text": "Yeah, should make posting slack transcripts much easier", - "ts": "1501801665.062694" + type: "message", + user: "U6JSSESES", + text: "Yeah, should make posting slack transcripts much easier", + ts: "1501801665.062694", }, { - "type": "message", - "user": "U5Z773QLZ", - "text": "Oooh a new discourse plugin <@U5Z773QLS> ???", - "ts": "1501801643.056375" + type: "message", + user: "U5Z773QLZ", + text: "Oooh a new discourse plugin <@U5Z773QLS> ???", + ts: "1501801643.056375", + }, + { type: "message", user: "U6E2W7R8C", text: "Which one?", ts: "1501801635.053761" }, + { + type: "message", + user: "U6JSSESES", + text: "So, who's interested in the new ?", + ts: "1501801629.052212", }, { - "type": "message", - "user": "U6E2W7R8C", - "text": "Which one?", - "ts": "1501801635.053761" + type: "message", + user: "U820GH3LA", + text: "I'm interested!!", + ts: "1501801634.053761", + thread_ts: "1501801629.052212", }, { - "type": "message", - "user": "U6JSSESES", - "text": "So, who's interested in the new ?", - "ts": "1501801629.052212" + text: "Check this out!", + username: "Test Community", + bot_id: "B6C6JNUDN", + attachments: [ + { + author_name: "@david", + fallback: "Discourse can now be integrated with Mattermost! - @david", + text: "Hey , what do you think about this?", + title: "Discourse can now be integrated with Mattermost! [Announcements] ", + id: 1, + title_link: + "http://localhost:3000/t/discourse-can-now-be-integrated-with-mattermost/51/4", + color: "283890", + mrkdwn_in: ["text"], + }, + ], + type: "message", + subtype: "bot_message", + ts: "1501615820.949638", }, { - "type": "message", - "user": "U820GH3LA", - "text": "I'm interested!!", - "ts": "1501801634.053761", - "thread_ts": "1501801629.052212" + type: "message", + user: "U5Z773QLS", + text: "Let’s try some *bold text* <@U5Z773QLZ> <@someotheruser>", + ts: "1501093331.439776", }, - { - "text": "Check this out!", - "username": "Test Community", - "bot_id": "B6C6JNUDN", - "attachments": [ - { - "author_name": "@david", - "fallback": "Discourse can now be integrated with Mattermost! - @david", - "text": "Hey , what do you think about this?", - "title": "Discourse can now be integrated with Mattermost! [Announcements] ", - "id": 1, - "title_link": "http://localhost:3000/t/discourse-can-now-be-integrated-with-mattermost/51/4", - "color": "283890", - "mrkdwn_in": [ - "text" - ] - } - ], - "type": "message", - "subtype": "bot_message", - "ts": "1501615820.949638" - }, - { - "type": "message", - "user": "U5Z773QLS", - "text": "Let’s try some *bold text* <@U5Z773QLZ> <@someotheruser>", - "ts": "1501093331.439776" - }, ] - } + end - let(:users_fixture) { + let(:users_fixture) do [ { id: "U6JSSESES", @@ -79,8 +71,8 @@ RSpec.describe DiscourseChatIntegration::Provider::SlackProvider::SlackTranscrip profile: { image_24: "https://example.com/avatar", display_name: "Threader", - real_name: "A. Threader" - } + real_name: "A. Threader", + }, }, { id: "U820GH3LA", @@ -88,8 +80,8 @@ RSpec.describe DiscourseChatIntegration::Provider::SlackProvider::SlackTranscrip profile: { image_24: "https://example.com/avatar", display_name: "Responder", - real_name: "A. Responder" - } + real_name: "A. Responder", + }, }, { id: "U5Z773QLS", @@ -97,8 +89,8 @@ RSpec.describe DiscourseChatIntegration::Provider::SlackProvider::SlackTranscrip profile: { image_24: "https://example.com/avatar", display_name: "awesomeguy", - real_name: "actually just a guy" - } + real_name: "actually just a guy", + }, }, { id: "U5Z773QLZ", @@ -106,196 +98,221 @@ RSpec.describe DiscourseChatIntegration::Provider::SlackProvider::SlackTranscrip profile: { image_24: "https://example.com/avatar", display_name: "", - real_name: "another guy" - } - } + real_name: "another guy", + }, + }, ] - } + end let(:transcript) { described_class.new(channel_name: "#general", channel_id: "G1234") } - before do - SiteSetting.chat_integration_slack_access_token = "abcde" - end + before { SiteSetting.chat_integration_slack_access_token = "abcde" } it "doesn't raise an error when there are no messages to guess" do transcript.instance_variable_set(:@messages, []) expect(transcript.guess_first_message(skip_messages: 1)).to eq(false) end - describe 'loading users' do - it 'loads users correctly' do - stub_request(:post, "https://slack.com/api/users.list") - .with(body: { token: "abcde", "cursor": nil, "limit": "200" }) - .to_return(status: 200, body: { ok: true, members: users_fixture, response_metadata: { next_cursor: "" } }.to_json) + describe "loading users" do + it "loads users correctly" do + stub_request(:post, "https://slack.com/api/users.list").with( + body: { + token: "abcde", + cursor: nil, + limit: "200", + }, + ).to_return( + status: 200, + body: { ok: true, members: users_fixture, response_metadata: { next_cursor: "" } }.to_json, + ) expect(transcript.load_user_data).to be_truthy end - it 'handles failed connection' do - stub_request(:post, "https://slack.com/api/users.list") - .to_return(status: 500, body: '') + it "handles failed connection" do + stub_request(:post, "https://slack.com/api/users.list").to_return(status: 500, body: "") expect(transcript.load_user_data).to eq(false) end - it 'handles slack failure' do - stub_request(:post, "https://slack.com/api/users.list") - .to_return(status: 200, body: { ok: false }.to_json) + it "handles slack failure" do + stub_request(:post, "https://slack.com/api/users.list").to_return( + status: 200, + body: { ok: false }.to_json, + ) expect(transcript.load_user_data).to eq(false) end end - context 'with loaded users' do + context "with loaded users" do before do - stub_request(:post, "https://slack.com/api/users.list") - .to_return(status: 200, body: { ok: true, members: users_fixture, response_metadata: { next_cursor: "" } }.to_json) + stub_request(:post, "https://slack.com/api/users.list").to_return( + status: 200, + body: { ok: true, members: users_fixture, response_metadata: { next_cursor: "" } }.to_json, + ) transcript.load_user_data end - describe 'loading history' do - it 'loads messages correctly' do - stub_request(:post, "https://slack.com/api/conversations.history") - .with(body: hash_including(token: "abcde", channel: 'G1234')) - .to_return(status: 200, body: { ok: true, messages: messages_fixture }.to_json) + describe "loading history" do + it "loads messages correctly" do + stub_request(:post, "https://slack.com/api/conversations.history").with( + body: hash_including(token: "abcde", channel: "G1234"), + ).to_return(status: 200, body: { ok: true, messages: messages_fixture }.to_json) - expect(transcript.load_chat_history).to be_truthy + expect(transcript.load_chat_history).to be_truthy end - it 'handles failed connection' do - stub_request(:post, "https://slack.com/api/conversations.history") - .to_return(status: 500, body: {}.to_json) + it "handles failed connection" do + stub_request(:post, "https://slack.com/api/conversations.history").to_return( + status: 500, + body: {}.to_json, + ) - expect(transcript.load_chat_history).to be_falsey + expect(transcript.load_chat_history).to be_falsey end - it 'handles slack failure' do - stub_request(:post, "https://slack.com/api/conversations.history") - .to_return(status: 200, body: { ok: false }.to_json) + it "handles slack failure" do + stub_request(:post, "https://slack.com/api/conversations.history").to_return( + status: 200, + body: { ok: false }.to_json, + ) - expect(transcript.load_chat_history).to be_falsey + expect(transcript.load_chat_history).to be_falsey end end - context 'with thread_ts specified' do - let(:thread_transcript) { described_class.new(channel_name: "#general", channel_id: "G1234", requested_thread_ts: "1501801629.052212") } + context "with thread_ts specified" do + let(:thread_transcript) do + described_class.new( + channel_name: "#general", + channel_id: "G1234", + requested_thread_ts: "1501801629.052212", + ) + end before do thread_transcript.load_user_data - stub_request(:post, "https://slack.com/api/conversations.replies") - .with(body: hash_including(token: "abcde", channel: 'G1234', ts: "1501801629.052212")) - .to_return(status: 200, body: { ok: true, messages: messages_fixture[3..4] }.to_json) + stub_request(:post, "https://slack.com/api/conversations.replies").with( + body: hash_including(token: "abcde", channel: "G1234", ts: "1501801629.052212"), + ).to_return(status: 200, body: { ok: true, messages: messages_fixture[3..4] }.to_json) thread_transcript.load_chat_history end - it 'includes messages in a thread' do + it "includes messages in a thread" do expect(thread_transcript.messages.length).to eq(2) end - it 'loads in chronological order' do # replies API presents messages in actual chronological order - expect(thread_transcript.messages.first.ts).to eq('1501801629.052212') + it "loads in chronological order" do # replies API presents messages in actual chronological order + expect(thread_transcript.messages.first.ts).to eq("1501801629.052212") end - it 'includes slack thread identifiers in body' do + it "includes slack thread identifiers in body" do text = thread_transcript.build_transcript expect(text).to include("") end - end - context 'with loaded messages' do + context "with loaded messages" do before do - stub_request(:post, "https://slack.com/api/conversations.history") - .with(body: hash_including(token: "abcde", channel: 'G1234')) - .to_return(status: 200, body: { ok: true, messages: messages_fixture }.to_json) + stub_request(:post, "https://slack.com/api/conversations.history").with( + body: hash_including(token: "abcde", channel: "G1234"), + ).to_return(status: 200, body: { ok: true, messages: messages_fixture }.to_json) transcript.load_chat_history end - it 'ignores messages in a thread' do + it "ignores messages in a thread" do expect(transcript.messages.length).to eq(6) end - it 'loads in chronological order' do # API presents in reverse chronological - expect(transcript.messages.first.ts).to eq('1501093331.439776') + it "loads in chronological order" do # API presents in reverse chronological + expect(transcript.messages.first.ts).to eq("1501093331.439776") end - it 'handles bold text' do + it "handles bold text" do expect(transcript.messages.first.text).to start_with("Let’s try some **bold text** ") end - it 'handles links' do - expect(transcript.messages[2].text).to eq("So, who's interested in the new [discourse plugin](https://meta.discourse.org)?") + it "handles links" do + expect(transcript.messages[2].text).to eq( + "So, who's interested in the new [discourse plugin](https://meta.discourse.org)?", + ) end - it 'includes attachments' do - expect(transcript.messages[1].attachments.first).to eq("Discourse can now be integrated with Mattermost! - @david") + it "includes attachments" do + expect(transcript.messages[1].attachments.first).to eq( + "Discourse can now be integrated with Mattermost! - @david", + ) end - it 'can generate URL' do - expect(transcript.messages.first.url).to eq("https://slack.com/archives/G1234/p1501093331439776") + it "can generate URL" do + expect(transcript.messages.first.url).to eq( + "https://slack.com/archives/G1234/p1501093331439776", + ) end - it 'includes attachments in raw text' do - transcript.set_first_message_by_ts('1501615820.949638') - expect(transcript.first_message.raw_text).to eq("Check this out!\n - Discourse can now be integrated with Mattermost! - @david\n") + it "includes attachments in raw text" do + transcript.set_first_message_by_ts("1501615820.949638") + expect(transcript.first_message.raw_text).to eq( + "Check this out!\n - Discourse can now be integrated with Mattermost! - @david\n", + ) end - it 'gives correct first and last messages' do + it "gives correct first and last messages" do expect(transcript.first_message_number).to eq(0) expect(transcript.last_message_number).to eq(transcript.messages.length - 1) - expect(transcript.first_message.ts).to eq('1501093331.439776') - expect(transcript.last_message.ts).to eq('1501801665.062694') + expect(transcript.first_message.ts).to eq("1501093331.439776") + expect(transcript.last_message.ts).to eq("1501801665.062694") end - it 'can change first and last messages by index' do + it "can change first and last messages by index" do expect(transcript.set_first_message_by_index(999)).to be_falsey expect(transcript.set_first_message_by_index(1)).to be_truthy expect(transcript.set_last_message_by_index(-2)).to be_truthy - expect(transcript.first_message.ts).to eq('1501615820.949638') - expect(transcript.last_message.ts).to eq('1501801643.056375') + expect(transcript.first_message.ts).to eq("1501615820.949638") + expect(transcript.last_message.ts).to eq("1501801643.056375") end - it 'can change first and last messages by ts' do - expect(transcript.set_first_message_by_ts('blah')).to be_falsey - expect(transcript.set_first_message_by_ts('1501615820.949638')).to be_truthy + it "can change first and last messages by ts" do + expect(transcript.set_first_message_by_ts("blah")).to be_falsey + expect(transcript.set_first_message_by_ts("1501615820.949638")).to be_truthy - expect(transcript.set_last_message_by_ts('1501801629.052212')).to be_truthy + expect(transcript.set_last_message_by_ts("1501801629.052212")).to be_truthy expect(transcript.first_message_number).to eq(1) expect(transcript.last_message_number).to eq(2) end - it 'can guess the first message' do + it "can guess the first message" do expect(transcript.guess_first_message(skip_messages: 1)).to eq(true) - expect(transcript.first_message.ts).to eq('1501801629.052212') + expect(transcript.first_message.ts).to eq("1501801629.052212") end - it 'handles usernames correctly' do - expect(transcript.first_message.username).to eq('awesomeguy') # Normal user - expect(transcript.messages[1].username).to eq('Test_Community') # Bot user + it "handles usernames correctly" do + expect(transcript.first_message.username).to eq("awesomeguy") # Normal user + expect(transcript.messages[1].username).to eq("Test_Community") # Bot user expect(transcript.messages[3].username).to eq(nil) # Unknown normal user # Normal user, display_name not set (fall back to real_name) - expect(transcript.messages[4].username).to eq('another_guy') + expect(transcript.messages[4].username).to eq("another_guy") end - it 'handles user mentions correctly' do + it "handles user mentions correctly" do # User with display_name not set, unrecognized user - expect(transcript.first_message.text).to \ - eq('Let’s try some **bold text** @another_guy @someotheruser') + expect(transcript.first_message.text).to eq( + "Let’s try some **bold text** @another_guy @someotheruser", + ) # Normal user - expect(transcript.messages[4].text).to \ - eq('Oooh a new discourse plugin @awesomeguy ???') + expect(transcript.messages[4].text).to eq("Oooh a new discourse plugin @awesomeguy ???") end - it 'handles avatars correctly' do + it "handles avatars correctly" do expect(transcript.first_message.avatar).to eq("https://example.com/avatar") # Normal user expect(transcript.messages[1].avatar).to eq(nil) # Bot user end - it 'creates a transcript correctly' do + it "creates a transcript correctly" do transcript.set_last_message_by_index(1) text = transcript.build_transcript @@ -317,7 +334,7 @@ RSpec.describe DiscourseChatIntegration::Provider::SlackProvider::SlackTranscrip expect(text).to eq(expected) end - it 'omits quote tags when disabled' do + it "omits quote tags when disabled" do transcript.set_last_message_by_index(1) text = transcript.build_transcript @@ -331,7 +348,7 @@ RSpec.describe DiscourseChatIntegration::Provider::SlackProvider::SlackTranscrip expect(text).not_to include("[/quote]") end - it 'creates the slack UI correctly' do + it "creates the slack UI correctly" do transcript.set_last_message_by_index(1) ui = transcript.build_slack_ui @@ -352,16 +369,17 @@ RSpec.describe DiscourseChatIntegration::Provider::SlackProvider::SlackTranscrip end describe "message formatting" do - it 'handles code block newlines' do - message = DiscourseChatIntegration::Provider::SlackProvider::SlackMessage.new( - { - "type" => "message", - "user" => "U5Z773QLS", - "text" => "Here is some code```my code\nwith newline```", - "ts" => "1501093331.439776" - }, - transcript - ) + it "handles code block newlines" do + message = + DiscourseChatIntegration::Provider::SlackProvider::SlackMessage.new( + { + "type" => "message", + "user" => "U5Z773QLS", + "text" => "Here is some code```my code\nwith newline```", + "ts" => "1501093331.439776", + }, + transcript, + ) expect(message.text).to eq(<<~MD) Here is some code ``` @@ -371,16 +389,18 @@ RSpec.describe DiscourseChatIntegration::Provider::SlackProvider::SlackTranscrip MD end - it 'handles multiple code blocks' do - message = DiscourseChatIntegration::Provider::SlackProvider::SlackMessage.new( - { - "type" => "message", - "user" => "U5Z773QLS", - "text" => "Here is some code```my code\nwith newline```and another```some more code```", - "ts" => "1501093331.439776" - }, - transcript - ) + it "handles multiple code blocks" do + message = + DiscourseChatIntegration::Provider::SlackProvider::SlackMessage.new( + { + "type" => "message", + "user" => "U5Z773QLS", + "text" => + "Here is some code```my code\nwith newline```and another```some more code```", + "ts" => "1501093331.439776", + }, + transcript, + ) expect(message.text).to eq(<<~MD) Here is some code ``` @@ -394,73 +414,83 @@ RSpec.describe DiscourseChatIntegration::Provider::SlackProvider::SlackTranscrip MD end - it 'handles strikethrough' do - message = DiscourseChatIntegration::Provider::SlackProvider::SlackMessage.new( - { - "type" => "message", - "user" => "U5Z773QLS", - "text" => "Some ~strikethrough~", - "ts" => "1501093331.439776" - }, - transcript - ) + it "handles strikethrough" do + message = + DiscourseChatIntegration::Provider::SlackProvider::SlackMessage.new( + { + "type" => "message", + "user" => "U5Z773QLS", + "text" => "Some ~strikethrough~", + "ts" => "1501093331.439776", + }, + transcript, + ) expect(message.text).to eq("Some ~~strikethrough~~") end - it 'handles slack links' do - message = DiscourseChatIntegration::Provider::SlackProvider::SlackMessage.new( - { - "type" => "message", - "user" => "U5Z773QLS", - "text" => "A link to , , , <#channel>, <@user>", - "ts" => "1501093331.439776" - }, - transcript + it "handles slack links" do + message = + DiscourseChatIntegration::Provider::SlackProvider::SlackMessage.new( + { + "type" => "message", + "user" => "U5Z773QLS", + "text" => + "A link to , , , <#channel>, <@user>", + "ts" => "1501093331.439776", + }, + transcript, + ) + expect(message.text).to eq( + "A link to [google](https://google.com), , , #channel, @user", ) - expect(message.text).to eq("A link to [google](https://google.com), , , #channel, @user") end - it 'does not format things inside backticks' do - message = DiscourseChatIntegration::Provider::SlackProvider::SlackMessage.new( - { - "type" => "message", - "user" => "U5Z773QLS", - "text" => "You can strikethrough like `~this~`, bold like `*this*` and link like `[https://example.com](https://example.com)`", - "ts" => "1501093331.439776" - }, - transcript + it "does not format things inside backticks" do + message = + DiscourseChatIntegration::Provider::SlackProvider::SlackMessage.new( + { + "type" => "message", + "user" => "U5Z773QLS", + "text" => + "You can strikethrough like `~this~`, bold like `*this*` and link like `[https://example.com](https://example.com)`", + "ts" => "1501093331.439776", + }, + transcript, + ) + expect(message.text).to eq( + "You can strikethrough like `~this~`, bold like `*this*` and link like `[https://example.com](https://example.com)`", ) - expect(message.text).to eq("You can strikethrough like `~this~`, bold like `*this*` and link like `[https://example.com](https://example.com)`") end - it 'unescapes html in backticks' do + it "unescapes html in backticks" do # Because Slack escapes HTML entities, even in backticks - message = DiscourseChatIntegration::Provider::SlackProvider::SlackMessage.new( - { - "type" => "message", - "user" => "U5Z773QLS", - "text" => "The code is `<stuff>`", - "ts" => "1501093331.439776" - }, - transcript - ) + message = + DiscourseChatIntegration::Provider::SlackProvider::SlackMessage.new( + { + "type" => "message", + "user" => "U5Z773QLS", + "text" => "The code is `<stuff>`", + "ts" => "1501093331.439776", + }, + transcript, + ) expect(message.text).to eq("The code is ``") end - it 'updates emoji dashes to underscores' do + it "updates emoji dashes to underscores" do # Discourse does not allow dashes in emoji names, so this helps communities have matching custom emojis - message = DiscourseChatIntegration::Provider::SlackProvider::SlackMessage.new( - { - "type" => "message", - "user" => "U5Z773QLS", - "text" => "This is :my-emoji:", - "ts" => "1501093331.439776" - }, - transcript - ) + message = + DiscourseChatIntegration::Provider::SlackProvider::SlackMessage.new( + { + "type" => "message", + "user" => "U5Z773QLS", + "text" => "This is :my-emoji:", + "ts" => "1501093331.439776", + }, + transcript, + ) expect(message.text).to eq("This is :my_emoji:") end end - end end diff --git a/spec/lib/discourse_chat_integration/provider/teams/teams_provider_spec.rb b/spec/lib/discourse_chat_integration/provider/teams/teams_provider_spec.rb index 450b368..dbf8d95 100644 --- a/spec/lib/discourse_chat_integration/provider/teams/teams_provider_spec.rb +++ b/spec/lib/discourse_chat_integration/provider/teams/teams_provider_spec.rb @@ -1,42 +1,47 @@ # frozen_string_literal: true -require 'rails_helper' +require "rails_helper" RSpec.describe DiscourseChatIntegration::Provider::TeamsProvider do let(:post) { Fabricate(:post) } - describe '.trigger_notifications' do - before do - SiteSetting.chat_integration_teams_enabled = true + describe ".trigger_notifications" do + before { SiteSetting.chat_integration_teams_enabled = true } + + let(:chan1) do + DiscourseChatIntegration::Channel.create!( + provider: "teams", + data: { + name: "discourse", + webhook_url: + "https://outlook.office.com/webhook/677980e4-e03b-4a5e-ad29-dc1ee0c32a80@9e9b5238-5ab2-496a-8e6a-e9cf05c7eb5c/IncomingWebhook/e7a1006ded44478992769d0c4f391e34/e028ca8a-e9c8-4c6c-a4d8-578f881a3cff", + }, + ) end - let(:chan1) { DiscourseChatIntegration::Channel.create!(provider: 'teams', data: { name: 'discourse', webhook_url: 'https://outlook.office.com/webhook/677980e4-e03b-4a5e-ad29-dc1ee0c32a80@9e9b5238-5ab2-496a-8e6a-e9cf05c7eb5c/IncomingWebhook/e7a1006ded44478992769d0c4f391e34/e028ca8a-e9c8-4c6c-a4d8-578f881a3cff' }) } - - it 'sends a webhook request' do - stub1 = stub_request(:post, chan1.data['webhook_url']).to_return(body: "1") + it "sends a webhook request" do + stub1 = stub_request(:post, chan1.data["webhook_url"]).to_return(body: "1") described_class.trigger_notification(post, chan1, nil) expect(stub1).to have_been_requested.once end - it 'handles errors correctly' do - stub1 = stub_request(:post, chan1.data['webhook_url']).to_return(status: 400, body: "{}") + it "handles errors correctly" do + stub1 = stub_request(:post, chan1.data["webhook_url"]).to_return(status: 400, body: "{}") expect(stub1).to have_been_requested.times(0) - expect { described_class.trigger_notification(post, chan1, nil) }.to raise_exception(::DiscourseChatIntegration::ProviderError) + expect { described_class.trigger_notification(post, chan1, nil) }.to raise_exception( + ::DiscourseChatIntegration::ProviderError, + ) expect(stub1).to have_been_requested.once end - describe 'with nil user.name' do - before do - post.user.update!(name: nil) - end + describe "with nil user.name" do + before { post.user.update!(name: nil) } - it 'handles nil username correctly' do + it "handles nil username correctly" do message = described_class.get_message(post) name = message[:sections].first[:facts].first[:name] expect(name).to eq("") end end - end - end diff --git a/spec/lib/discourse_chat_integration/provider/telegram/telegram_command_controller_spec.rb b/spec/lib/discourse_chat_integration/provider/telegram/telegram_command_controller_spec.rb index 33288eb..62e3ff3 100644 --- a/spec/lib/discourse_chat_integration/provider/telegram/telegram_command_controller_spec.rb +++ b/spec/lib/discourse_chat_integration/provider/telegram/telegram_command_controller_spec.rb @@ -1,32 +1,44 @@ # frozen_string_literal: true -require 'rails_helper' +require "rails_helper" -describe 'Telegram Command Controller', type: :request do +describe "Telegram Command Controller", type: :request do let(:category) { Fabricate(:category) } - let!(:chan1) { DiscourseChatIntegration::Channel.create!(provider: 'telegram', data: { name: 'Amazing Channel', chat_id: '123' }) } - let!(:webhook_stub) { stub_request(:post, 'https://api.telegram.org/botTOKEN/setWebhook').to_return(body: "{\"ok\":true}") } + let!(:chan1) do + DiscourseChatIntegration::Channel.create!( + provider: "telegram", + data: { + name: "Amazing Channel", + chat_id: "123", + }, + ) + end + let!(:webhook_stub) do + stub_request(:post, "https://api.telegram.org/botTOKEN/setWebhook").to_return( + body: "{\"ok\":true}", + ) + end - describe 'with plugin disabled' do - it 'should return a 404' do - post '/chat-integration/telegram/command/abcd.json' + describe "with plugin disabled" do + it "should return a 404" do + post "/chat-integration/telegram/command/abcd.json" expect(response.status).to eq(404) end end - describe 'with plugin enabled and provider disabled' do + describe "with plugin enabled and provider disabled" do before do SiteSetting.chat_integration_enabled = true SiteSetting.chat_integration_telegram_enabled = false end - it 'should return a 404' do - post '/chat-integration/telegram/command/abcd.json' + it "should return a 404" do + post "/chat-integration/telegram/command/abcd.json" expect(response.status).to eq(404) end end - describe 'slash commands endpoint' do + describe "slash commands endpoint" do before do SiteSetting.chat_integration_enabled = true SiteSetting.chat_integration_telegram_access_token = "TOKEN" @@ -34,85 +46,122 @@ describe 'Telegram Command Controller', type: :request do SiteSetting.chat_integration_telegram_secret = "shhh" end - let!(:stub) { stub_request(:post, 'https://api.telegram.org/botTOKEN/sendMessage').to_return(body: "{\"ok\":true}") } + let!(:stub) do + stub_request(:post, "https://api.telegram.org/botTOKEN/sendMessage").to_return( + body: "{\"ok\":true}", + ) + end - describe 'when forum is private' do - it 'should not redirect to login page' do + describe "when forum is private" do + it "should not redirect to login page" do SiteSetting.login_required = true - post '/chat-integration/telegram/command/shhh.json', params: { - message: { chat: { id: 123 }, text: '/help' } - } + post "/chat-integration/telegram/command/shhh.json", + params: { + message: { + chat: { + id: 123, + }, + text: "/help", + }, + } expect(response.status).to eq(200) end end - describe 'when the token is invalid' do - it 'should raise the right error' do - post '/chat-integration/telegram/command/blah.json', params: { - message: { chat: { id: 123 }, text: '/help' } - } + describe "when the token is invalid" do + it "should raise the right error" do + post "/chat-integration/telegram/command/blah.json", + params: { + message: { + chat: { + id: 123, + }, + text: "/help", + }, + } expect(response.status).to eq(403) end end - describe 'when token has not been set' do - it 'should raise the right error' do + describe "when token has not been set" do + it "should raise the right error" do SiteSetting.chat_integration_telegram_access_token = "" - post '/chat-integration/telegram/command/blah.json', params: { - message: { chat: { id: 123 }, text: '/help' } - } + post "/chat-integration/telegram/command/blah.json", + params: { + message: { + chat: { + id: 123, + }, + text: "/help", + }, + } expect(response.status).to eq(403) end end - describe 'when token is valid' do + describe "when token is valid" do let(:token) { "TOKEN" } - before do - SiteSetting.chat_integration_telegram_enable_slash_commands = true - end + before { SiteSetting.chat_integration_telegram_enable_slash_commands = true } - describe 'add new rule' do - - it 'should add a new rule correctly' do - post '/chat-integration/telegram/command/shhh.json', params: { - message: { chat: { id: 123 }, text: "/watch #{category.slug}" } - } + describe "add new rule" do + it "should add a new rule correctly" do + post "/chat-integration/telegram/command/shhh.json", + params: { + message: { + chat: { + id: 123, + }, + text: "/watch #{category.slug}", + }, + } expect(response.status).to eq(200) expect(stub).to have_been_requested.once rule = DiscourseChatIntegration::Rule.all.first expect(rule.channel).to eq(chan1) - expect(rule.filter).to eq('watch') + expect(rule.filter).to eq("watch") expect(rule.category_id).to eq(category.id) expect(rule.tags).to eq(nil) end - it 'should add a new rule correctly using group chat syntax' do - post '/chat-integration/telegram/command/shhh.json', params: { - message: { chat: { id: 123 }, text: "/watch@my-awesome-bot #{category.slug}" } - } + it "should add a new rule correctly using group chat syntax" do + post "/chat-integration/telegram/command/shhh.json", + params: { + message: { + chat: { + id: 123, + }, + text: "/watch@my-awesome-bot #{category.slug}", + }, + } expect(response.status).to eq(200) expect(stub).to have_been_requested.once rule = DiscourseChatIntegration::Rule.all.first expect(rule.channel).to eq(chan1) - expect(rule.filter).to eq('watch') + expect(rule.filter).to eq("watch") expect(rule.category_id).to eq(category.id) expect(rule.tags).to eq(nil) end - describe 'from an unknown channel' do - it 'does nothing' do - post '/chat-integration/telegram/command/shhh.json', params: { - message: { chat: { id: 456 }, text: "/watch #{category.slug}" } - } + describe "from an unknown channel" do + it "does nothing" do + post "/chat-integration/telegram/command/shhh.json", + params: { + message: { + chat: { + id: 456, + }, + text: "/watch #{category.slug}", + }, + } expect(DiscourseChatIntegration::Rule.all.size).to eq(0) expect(DiscourseChatIntegration::Channel.all.size).to eq(1) @@ -121,16 +170,28 @@ describe 'Telegram Command Controller', type: :request do end it "should respond only to a specific command in a broadcast channel" do - post '/chat-integration/telegram/command/shhh.json', params: { - channel_post: { chat: { id: 123 }, text: "something" } - } + post "/chat-integration/telegram/command/shhh.json", + params: { + channel_post: { + chat: { + id: 123, + }, + text: "something", + }, + } expect(response.status).to eq(200) expect(stub).to have_been_requested.times(0) - post '/chat-integration/telegram/command/shhh.json', params: { - channel_post: { chat: { id: 123 }, text: "/getchatid" } - } + post "/chat-integration/telegram/command/shhh.json", + params: { + channel_post: { + chat: { + id: 123, + }, + text: "/getchatid", + }, + } expect(response.status).to eq(200) expect(stub).to have_been_requested.times(1) @@ -138,9 +199,14 @@ describe 'Telegram Command Controller', type: :request do context "when 'text' is missing" do it "does not break" do - post '/chat-integration/telegram/command/shhh.json', params: { - message: { chat: { id: 123 } } - } + post "/chat-integration/telegram/command/shhh.json", + params: { + message: { + chat: { + id: 123, + }, + }, + } expect(response).to have_http_status :ok expect(DiscourseChatIntegration::Rule.count).to eq(0) diff --git a/spec/lib/discourse_chat_integration/provider/telegram/telegram_provider_spec.rb b/spec/lib/discourse_chat_integration/provider/telegram/telegram_provider_spec.rb index 8e6f140..aec7115 100644 --- a/spec/lib/discourse_chat_integration/provider/telegram/telegram_provider_spec.rb +++ b/spec/lib/discourse_chat_integration/provider/telegram/telegram_provider_spec.rb @@ -1,33 +1,51 @@ # frozen_string_literal: true -require 'rails_helper' +require "rails_helper" RSpec.describe DiscourseChatIntegration::Provider::TelegramProvider do let(:post) { Fabricate(:post) } - let!(:webhook_stub) { stub_request(:post, 'https://api.telegram.org/botTOKEN/setWebhook').to_return(body: "{\"ok\":true}") } + let!(:webhook_stub) do + stub_request(:post, "https://api.telegram.org/botTOKEN/setWebhook").to_return( + body: "{\"ok\":true}", + ) + end - describe '.trigger_notifications' do + describe ".trigger_notifications" do before do SiteSetting.chat_integration_telegram_access_token = "TOKEN" SiteSetting.chat_integration_telegram_enabled = true - SiteSetting.chat_integration_telegram_secret = 'shhh' + SiteSetting.chat_integration_telegram_secret = "shhh" end - let(:chan1) { DiscourseChatIntegration::Channel.create!(provider: 'telegram', data: { name: "Awesome Channel", chat_id: '123' }) } + let(:chan1) do + DiscourseChatIntegration::Channel.create!( + provider: "telegram", + data: { + name: "Awesome Channel", + chat_id: "123", + }, + ) + end - it 'sends a webhook request' do - stub1 = stub_request(:post, 'https://api.telegram.org/botTOKEN/sendMessage').to_return(body: "{\"ok\":true}") + it "sends a webhook request" do + stub1 = + stub_request(:post, "https://api.telegram.org/botTOKEN/sendMessage").to_return( + body: "{\"ok\":true}", + ) described_class.trigger_notification(post, chan1, nil) expect(stub1).to have_been_requested.once end - it 'handles errors correctly' do - stub1 = stub_request(:post, 'https://api.telegram.org/botTOKEN/sendMessage').to_return(body: "{\"ok\":false, \"description\":\"chat not found\"}") + it "handles errors correctly" do + stub1 = + stub_request(:post, "https://api.telegram.org/botTOKEN/sendMessage").to_return( + body: "{\"ok\":false, \"description\":\"chat not found\"}", + ) expect(stub1).to have_been_requested.times(0) - expect { described_class.trigger_notification(post, chan1, nil) }.to raise_exception(::DiscourseChatIntegration::ProviderError) + expect { described_class.trigger_notification(post, chan1, nil) }.to raise_exception( + ::DiscourseChatIntegration::ProviderError, + ) expect(stub1).to have_been_requested.once end - end - end diff --git a/spec/lib/discourse_chat_integration/provider/webex/webex_provider_spec.rb b/spec/lib/discourse_chat_integration/provider/webex/webex_provider_spec.rb index 2940702..d645c01 100644 --- a/spec/lib/discourse_chat_integration/provider/webex/webex_provider_spec.rb +++ b/spec/lib/discourse_chat_integration/provider/webex/webex_provider_spec.rb @@ -1,30 +1,37 @@ # frozen_string_literal: true -require 'rails_helper' +require "rails_helper" RSpec.describe DiscourseChatIntegration::Provider::WebexProvider do let(:post) { Fabricate(:post) } - describe '.trigger_notifications' do - before do - SiteSetting.chat_integration_webex_enabled = true + describe ".trigger_notifications" do + before { SiteSetting.chat_integration_webex_enabled = true } + + let(:chan1) do + DiscourseChatIntegration::Channel.create!( + provider: "webex", + data: { + name: "discourse", + webhook_url: + "https://webexapis.com/v1/webhooks/incoming/jAHJjVVQ1cgEwb4ikQQawIrGdUtlocKA9fSNvIyADQoYo0mI70pztWUDOu22gDRPJOEJtCsc688zi1RMa", + }, + ) end - let(:chan1) { DiscourseChatIntegration::Channel.create!(provider: 'webex', data: { name: 'discourse', webhook_url: 'https://webexapis.com/v1/webhooks/incoming/jAHJjVVQ1cgEwb4ikQQawIrGdUtlocKA9fSNvIyADQoYo0mI70pztWUDOu22gDRPJOEJtCsc688zi1RMa' }) } - - it 'sends a webhook request' do - stub1 = stub_request(:post, chan1.data['webhook_url']).to_return(body: "1") + it "sends a webhook request" do + stub1 = stub_request(:post, chan1.data["webhook_url"]).to_return(body: "1") described_class.trigger_notification(post, chan1, nil) expect(stub1).to have_been_requested.once end - it 'handles errors correctly' do - stub1 = stub_request(:post, chan1.data['webhook_url']).to_return(status: 400, body: "{}") + it "handles errors correctly" do + stub1 = stub_request(:post, chan1.data["webhook_url"]).to_return(status: 400, body: "{}") expect(stub1).to have_been_requested.times(0) - expect { described_class.trigger_notification(post, chan1, nil) }.to raise_exception(::DiscourseChatIntegration::ProviderError) + expect { described_class.trigger_notification(post, chan1, nil) }.to raise_exception( + ::DiscourseChatIntegration::ProviderError, + ) expect(stub1).to have_been_requested.once end - end - end diff --git a/spec/lib/discourse_chat_integration/provider/zulip/zulip_provider_spec.rb b/spec/lib/discourse_chat_integration/provider/zulip/zulip_provider_spec.rb index e162c37..77c5f47 100644 --- a/spec/lib/discourse_chat_integration/provider/zulip/zulip_provider_spec.rb +++ b/spec/lib/discourse_chat_integration/provider/zulip/zulip_provider_spec.rb @@ -1,11 +1,11 @@ # frozen_string_literal: true -require 'rails_helper' +require "rails_helper" RSpec.describe DiscourseChatIntegration::Provider::ZulipProvider do let(:post) { Fabricate(:post) } - describe '.trigger_notifications' do + describe ".trigger_notifications" do before do SiteSetting.chat_integration_zulip_enabled = true SiteSetting.chat_integration_zulip_server = "https://hello.world" @@ -13,21 +13,33 @@ RSpec.describe DiscourseChatIntegration::Provider::ZulipProvider do SiteSetting.chat_integration_zulip_bot_api_key = "secret" end - let(:chan1) { DiscourseChatIntegration::Channel.create!(provider: 'zulip', data: { stream: "general", subject: "Discourse Notifications" }) } + let(:chan1) do + DiscourseChatIntegration::Channel.create!( + provider: "zulip", + data: { + stream: "general", + subject: "Discourse Notifications", + }, + ) + end - it 'sends a webhook request' do - stub1 = stub_request(:post, 'https://hello.world/api/v1/messages').to_return(status: 200) + it "sends a webhook request" do + stub1 = stub_request(:post, "https://hello.world/api/v1/messages").to_return(status: 200) described_class.trigger_notification(post, chan1, nil) expect(stub1).to have_been_requested.once end - it 'handles errors correctly' do - stub1 = stub_request(:post, 'https://hello.world/api/v1/messages').to_return(status: 400, body: '{}') + it "handles errors correctly" do + stub1 = + stub_request(:post, "https://hello.world/api/v1/messages").to_return( + status: 400, + body: "{}", + ) expect(stub1).to have_been_requested.times(0) - expect { described_class.trigger_notification(post, chan1, nil) }.to raise_exception(::DiscourseChatIntegration::ProviderError) + expect { described_class.trigger_notification(post, chan1, nil) }.to raise_exception( + ::DiscourseChatIntegration::ProviderError, + ) expect(stub1).to have_been_requested.once end - end - end diff --git a/spec/models/channel_spec.rb b/spec/models/channel_spec.rb index b636ff4..612cb55 100644 --- a/spec/models/channel_spec.rb +++ b/spec/models/channel_spec.rb @@ -1,13 +1,13 @@ # frozen_string_literal: true -require 'rails_helper' -require_relative '../dummy_provider' +require "rails_helper" +require_relative "../dummy_provider" RSpec.describe DiscourseChatIntegration::Channel do include_context "with dummy provider" include_context "with validated dummy provider" - it 'should save and load successfully' do + it "should save and load successfully" do expect(DiscourseChatIntegration::Channel.all.length).to eq(0) chan = DiscourseChatIntegration::Channel.create(provider: "dummy") @@ -16,49 +16,53 @@ RSpec.describe DiscourseChatIntegration::Channel do loadedChan = DiscourseChatIntegration::Channel.find(chan.id) - expect(loadedChan.provider).to eq('dummy') - + expect(loadedChan.provider).to eq("dummy") end - it 'should edit successfully' do + it "should edit successfully" do channel = DiscourseChatIntegration::Channel.create!(provider: "dummy2", data: { val: "hello" }) expect(channel.valid?).to eq(true) channel.save! end - it 'can be filtered by provider' do - channel1 = DiscourseChatIntegration::Channel.create!(provider: 'dummy') - channel2 = DiscourseChatIntegration::Channel.create!(provider: 'dummy2', data: { val: "blah" }) - channel3 = DiscourseChatIntegration::Channel.create!(provider: 'dummy2', data: { val: "blah2" }) + it "can be filtered by provider" do + channel1 = DiscourseChatIntegration::Channel.create!(provider: "dummy") + channel2 = DiscourseChatIntegration::Channel.create!(provider: "dummy2", data: { val: "blah" }) + channel3 = DiscourseChatIntegration::Channel.create!(provider: "dummy2", data: { val: "blah2" }) expect(DiscourseChatIntegration::Channel.all.length).to eq(3) - expect(DiscourseChatIntegration::Channel.with_provider('dummy2').length).to eq(2) - expect(DiscourseChatIntegration::Channel.with_provider('dummy').length).to eq(1) + expect(DiscourseChatIntegration::Channel.with_provider("dummy2").length).to eq(2) + expect(DiscourseChatIntegration::Channel.with_provider("dummy").length).to eq(1) end - it 'can be filtered by data value' do - channel2 = DiscourseChatIntegration::Channel.create!(provider: 'dummy2', data: { val: "foo" }) - channel3 = DiscourseChatIntegration::Channel.create!(provider: 'dummy2', data: { val: "blah" }) + it "can be filtered by data value" do + channel2 = DiscourseChatIntegration::Channel.create!(provider: "dummy2", data: { val: "foo" }) + channel3 = DiscourseChatIntegration::Channel.create!(provider: "dummy2", data: { val: "blah" }) expect(DiscourseChatIntegration::Channel.all.length).to eq(2) - for_provider = DiscourseChatIntegration::Channel.with_provider('dummy2') + for_provider = DiscourseChatIntegration::Channel.with_provider("dummy2") expect(for_provider.length).to eq(2) - expect(DiscourseChatIntegration::Channel.with_provider('dummy2').with_data_value('val', 'blah').length).to eq(1) + expect( + DiscourseChatIntegration::Channel + .with_provider("dummy2") + .with_data_value("val", "blah") + .length, + ).to eq(1) end - it 'can find its own rules' do - channel = DiscourseChatIntegration::Channel.create(provider: 'dummy') + it "can find its own rules" do + channel = DiscourseChatIntegration::Channel.create(provider: "dummy") expect(channel.rules.size).to eq(0) DiscourseChatIntegration::Rule.create(channel: channel) DiscourseChatIntegration::Rule.create(channel: channel) expect(channel.rules.size).to eq(2) end - it 'destroys its rules on destroy' do - channel = DiscourseChatIntegration::Channel.create(provider: 'dummy') + it "destroys its rules on destroy" do + channel = DiscourseChatIntegration::Channel.create(provider: "dummy") expect(channel.rules.size).to eq(0) rule1 = DiscourseChatIntegration::Rule.create(channel: channel) rule2 = DiscourseChatIntegration::Rule.create(channel: channel) @@ -68,42 +72,48 @@ RSpec.describe DiscourseChatIntegration::Channel do expect(DiscourseChatIntegration::Rule.with_channel(channel).exists?).to eq(false) end - describe 'validations' do - - it 'validates provider correctly' do + describe "validations" do + it "validates provider correctly" do channel = DiscourseChatIntegration::Channel.create!(provider: "dummy") expect(channel.valid?).to eq(true) - channel.provider = 'somerandomprovider' + channel.provider = "somerandomprovider" expect(channel.valid?).to eq(false) end - it 'succeeds with valid data' do + it "succeeds with valid data" do channel2 = DiscourseChatIntegration::Channel.new(provider: "dummy2", data: { val: "hello" }) expect(channel2.valid?).to eq(true) end - it 'disallows invalid data' do - channel2 = DiscourseChatIntegration::Channel.new(provider: "dummy2", data: { val: ' ' }) + it "disallows invalid data" do + channel2 = DiscourseChatIntegration::Channel.new(provider: "dummy2", data: { val: " " }) expect(channel2.valid?).to eq(false) end - it 'disallows unknown keys' do - channel2 = DiscourseChatIntegration::Channel.new(provider: "dummy2", data: { val: "hello", unknown: "world" }) + it "disallows unknown keys" do + channel2 = + DiscourseChatIntegration::Channel.new( + provider: "dummy2", + data: { + val: "hello", + unknown: "world", + }, + ) expect(channel2.valid?).to eq(false) end - it 'requires all keys' do + it "requires all keys" do channel2 = DiscourseChatIntegration::Channel.new(provider: "dummy2", data: {}) expect(channel2.valid?).to eq(false) end - it 'disallows duplicate channels' do - channel1 = DiscourseChatIntegration::Channel.create(provider: "dummy2", data: { val: "hello" }) + it "disallows duplicate channels" do + channel1 = + DiscourseChatIntegration::Channel.create(provider: "dummy2", data: { val: "hello" }) channel2 = DiscourseChatIntegration::Channel.new(provider: "dummy2", data: { val: "hello" }) expect(channel2.valid?).to eq(false) channel2.data[:val] = "hello2" expect(channel2.valid?).to eq(true) end - end end diff --git a/spec/models/rule_spec.rb b/spec/models/rule_spec.rb index 45e97f2..6ae96c3 100644 --- a/spec/models/rule_spec.rb +++ b/spec/models/rule_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require 'rails_helper' -require_relative '../dummy_provider' +require "rails_helper" +require_relative "../dummy_provider" RSpec.describe DiscourseChatIntegration::Rule do include_context "with dummy provider" @@ -9,31 +9,34 @@ RSpec.describe DiscourseChatIntegration::Rule do let(:tag1) { Fabricate(:tag) } let(:tag2) { Fabricate(:tag) } - let(:channel) { DiscourseChatIntegration::Channel.create(provider: 'dummy') } + let(:channel) { DiscourseChatIntegration::Channel.create(provider: "dummy") } let(:category) { Fabricate(:category) } let(:group) { Fabricate(:group) } - describe '.alloc_key' do - it 'should return sequential numbers' do + describe ".alloc_key" do + it "should return sequential numbers" do expect(DiscourseChatIntegration::Rule.create(channel: channel).key).to eq("rule:1") expect(DiscourseChatIntegration::Rule.create(channel: channel).key).to eq("rule:2") expect(DiscourseChatIntegration::Rule.create(channel: channel).key).to eq("rule:3") end end - it 'should convert between channel and channel_id successfully' do + it "should convert between channel and channel_id successfully" do rule = DiscourseChatIntegration::Rule.create(channel: channel) expect(rule.channel_id).to eq(channel.id) expect(rule.channel.id).to eq(channel.id) end - it 'should save and load successfully' do + it "should save and load successfully" do expect(DiscourseChatIntegration::Rule.all.length).to eq(0) - rule = DiscourseChatIntegration::Rule.create(channel: channel, - category_id: category.id, - tags: [tag1.name, tag2.name], - filter: 'watch') + rule = + DiscourseChatIntegration::Rule.create( + channel: channel, + category_id: category.id, + tags: [tag1.name, tag2.name], + filter: "watch", + ) expect(DiscourseChatIntegration::Rule.all.length).to eq(1) @@ -42,18 +45,20 @@ RSpec.describe DiscourseChatIntegration::Rule do expect(loadedRule.channel.id).to eq(channel.id) expect(loadedRule.category_id).to eq(category.id) expect(loadedRule.tags).to contain_exactly(tag1.name, tag2.name) - expect(loadedRule.filter).to eq('watch') - + expect(loadedRule.filter).to eq("watch") end - describe 'general operations' do + describe "general operations" do before do - rule = DiscourseChatIntegration::Rule.create(channel: channel, - category_id: category.id, - tags: [tag1.name, tag2.name]) + rule = + DiscourseChatIntegration::Rule.create( + channel: channel, + category_id: category.id, + tags: [tag1.name, tag2.name], + ) end - it 'can be modified' do + it "can be modified" do rule = DiscourseChatIntegration::Rule.all.first rule.tags = [tag1.name] @@ -63,7 +68,7 @@ RSpec.describe DiscourseChatIntegration::Rule do expect(rule.tags).to contain_exactly(tag1.name) end - it 'can be deleted' do + it "can be deleted" do DiscourseChatIntegration::Rule.new(channel: channel).save! expect(DiscourseChatIntegration::Rule.all.length).to eq(2) @@ -73,7 +78,7 @@ RSpec.describe DiscourseChatIntegration::Rule do expect(DiscourseChatIntegration::Rule.all.length).to eq(1) end - it 'can delete all' do + it "can delete all" do DiscourseChatIntegration::Rule.create(channel: channel) DiscourseChatIntegration::Rule.create(channel: channel) DiscourseChatIntegration::Rule.create(channel: channel) @@ -86,9 +91,9 @@ RSpec.describe DiscourseChatIntegration::Rule do expect(DiscourseChatIntegration::Rule.all.length).to eq(0) end - it 'can be filtered by channel' do - channel2 = DiscourseChatIntegration::Channel.create(provider: 'dummy') - channel3 = DiscourseChatIntegration::Channel.create(provider: 'dummy') + it "can be filtered by channel" do + channel2 = DiscourseChatIntegration::Channel.create(provider: "dummy") + channel3 = DiscourseChatIntegration::Channel.create(provider: "dummy") rule2 = DiscourseChatIntegration::Rule.create(channel: channel) rule3 = DiscourseChatIntegration::Rule.create(channel: channel) @@ -101,7 +106,7 @@ RSpec.describe DiscourseChatIntegration::Rule do expect(DiscourseChatIntegration::Rule.with_channel(channel2).length).to eq(1) end - it 'can be filtered by category' do + it "can be filtered by category" do rule2 = DiscourseChatIntegration::Rule.create(channel: channel, category_id: category.id) rule3 = DiscourseChatIntegration::Rule.create(channel: channel, category_id: nil) @@ -111,11 +116,21 @@ RSpec.describe DiscourseChatIntegration::Rule do expect(DiscourseChatIntegration::Rule.with_category_id(nil).length).to eq(1) end - it 'can be filtered by group' do + it "can be filtered by group" do group1 = Fabricate(:group) group2 = Fabricate(:group) - rule2 = DiscourseChatIntegration::Rule.create!(channel: channel, type: 'group_message', group_id: group1.id) - rule3 = DiscourseChatIntegration::Rule.create!(channel: channel, type: 'group_message', group_id: group2.id) + rule2 = + DiscourseChatIntegration::Rule.create!( + channel: channel, + type: "group_message", + group_id: group1.id, + ) + rule3 = + DiscourseChatIntegration::Rule.create!( + channel: channel, + type: "group_message", + group_id: group2.id, + ) expect(DiscourseChatIntegration::Rule.all.length).to eq(3) @@ -125,42 +140,55 @@ RSpec.describe DiscourseChatIntegration::Rule do expect(DiscourseChatIntegration::Rule.with_group_ids([group2.id]).length).to eq(1) end - it 'can be filtered by type' do + it "can be filtered by type" do group1 = Fabricate(:group) - rule2 = DiscourseChatIntegration::Rule.create!(channel: channel, type: 'group_message', group_id: group1.id) - rule3 = DiscourseChatIntegration::Rule.create!(channel: channel, type: 'group_mention', group_id: group1.id) + rule2 = + DiscourseChatIntegration::Rule.create!( + channel: channel, + type: "group_message", + group_id: group1.id, + ) + rule3 = + DiscourseChatIntegration::Rule.create!( + channel: channel, + type: "group_mention", + group_id: group1.id, + ) expect(DiscourseChatIntegration::Rule.all.length).to eq(3) - expect(DiscourseChatIntegration::Rule.with_type('group_message').length).to eq(1) - expect(DiscourseChatIntegration::Rule.with_type('group_mention').length).to eq(1) - expect(DiscourseChatIntegration::Rule.with_type('normal').length).to eq(1) + expect(DiscourseChatIntegration::Rule.with_type("group_message").length).to eq(1) + expect(DiscourseChatIntegration::Rule.with_type("group_mention").length).to eq(1) + expect(DiscourseChatIntegration::Rule.with_type("normal").length).to eq(1) end - it 'can be sorted by precedence' do - rule2 = DiscourseChatIntegration::Rule.create(channel: channel, filter: 'mute') - rule3 = DiscourseChatIntegration::Rule.create(channel: channel, filter: 'follow') - rule4 = DiscourseChatIntegration::Rule.create(channel: channel, filter: 'thread') - rule5 = DiscourseChatIntegration::Rule.create(channel: channel, filter: 'mute') + it "can be sorted by precedence" do + rule2 = DiscourseChatIntegration::Rule.create(channel: channel, filter: "mute") + rule3 = DiscourseChatIntegration::Rule.create(channel: channel, filter: "follow") + rule4 = DiscourseChatIntegration::Rule.create(channel: channel, filter: "thread") + rule5 = DiscourseChatIntegration::Rule.create(channel: channel, filter: "mute") expect(DiscourseChatIntegration::Rule.all.length).to eq(5) - expect(DiscourseChatIntegration::Rule.all.order_by_precedence.map(&:filter)).to eq(["mute", "mute", "thread", "watch", "follow"]) + expect(DiscourseChatIntegration::Rule.all.order_by_precedence.map(&:filter)).to eq( + %w[mute mute thread watch follow], + ) end end - describe 'validations' do - + describe "validations" do let(:rule) do - DiscourseChatIntegration::Rule.create(filter: 'watch', - channel: channel, - category_id: category.id) + DiscourseChatIntegration::Rule.create( + filter: "watch", + channel: channel, + category_id: category.id, + ) end - it 'validates channel correctly' do + it "validates channel correctly" do expect(rule.valid?).to eq(true) - rule.channel_id = 'blahblahblah' + rule.channel_id = "blahblahblah" expect(rule.valid?).to eq(false) rule.channel_id = -1 expect(rule.valid?).to eq(false) @@ -175,7 +203,7 @@ RSpec.describe DiscourseChatIntegration::Rule do expect(rule.valid?).to eq(true) end - it 'validates group correctly' do + it "validates group correctly" do rule.category_id = nil rule.group_id = group.id rule.type = "group_message" @@ -184,42 +212,41 @@ RSpec.describe DiscourseChatIntegration::Rule do expect(rule.valid?).to eq(false) end - it 'validates category correctly' do + it "validates category correctly" do expect(rule.valid?).to eq(true) rule.category_id = -99 expect(rule.valid?).to eq(false) end - it 'validates filter correctly' do + it "validates filter correctly" do expect(rule.valid?).to eq(true) - rule.filter = 'thread' + rule.filter = "thread" expect(rule.valid?).to eq(true) - rule.filter = 'follow' + rule.filter = "follow" expect(rule.valid?).to eq(true) - rule.filter = 'mute' + rule.filter = "mute" expect(rule.valid?).to eq(true) - rule.filter = '' + rule.filter = "" expect(rule.valid?).to eq(false) - rule.filter = 'somerandomstring' + rule.filter = "somerandomstring" expect(rule.valid?).to eq(false) end - it 'validates tags correctly' do + it "validates tags correctly" do expect(rule.valid?).to eq(true) rule.tags = [] expect(rule.valid?).to eq(true) rule.tags = [tag1.name] expect(rule.valid?).to eq(true) - rule.tags = [tag1.name, 'blah'] + rule.tags = [tag1.name, "blah"] expect(rule.valid?).to eq(false) end it "doesn't allow save when invalid" do expect(rule.valid?).to eq(true) - rule.filter = 'somerandomfilter' + rule.filter = "somerandomfilter" expect(rule.valid?).to eq(false) expect(rule.save).to eq(false) end - end end diff --git a/spec/requests/chat_controller_spec.rb b/spec/requests/chat_controller_spec.rb index 90960f5..a1cce8d 100644 --- a/spec/requests/chat_controller_spec.rb +++ b/spec/requests/chat_controller_spec.rb @@ -1,34 +1,31 @@ # frozen_string_literal: true -require 'rails_helper' -require_relative '../dummy_provider' - -describe 'Chat Controller', type: :request do +require "rails_helper" +require_relative "../dummy_provider" +describe "Chat Controller", type: :request do let(:topic) { Fabricate(:post).topic } let(:admin) { Fabricate(:admin) } let(:category) { Fabricate(:category) } let(:category2) { Fabricate(:category) } let(:tag) { Fabricate(:tag) } - let(:channel) { DiscourseChatIntegration::Channel.create(provider: 'dummy') } + let(:channel) { DiscourseChatIntegration::Channel.create(provider: "dummy") } include_context "with dummy provider" include_context "with validated dummy provider" - before do - SiteSetting.chat_integration_enabled = true - end + before { SiteSetting.chat_integration_enabled = true } - shared_examples 'admin constraints' do |action, route| - context 'when user is not signed in' do - it 'should raise the right error' do + shared_examples "admin constraints" do |action, route| + context "when user is not signed in" do + it "should raise the right error" do public_send(action, route) expect(response.status).to eq(404) end end - context 'when user is not an admin' do - it 'should raise the right error' do + context "when user is not an admin" do + it "should raise the right error" do sign_in(Fabricate(:user)) public_send(action, route) expect(response.status).to eq(404) @@ -36,154 +33,168 @@ describe 'Chat Controller', type: :request do end end - describe 'listing providers' do - include_examples 'admin constraints', 'get', '/admin/plugins/chat-integration/providers.json' + describe "listing providers" do + include_examples "admin constraints", "get", "/admin/plugins/chat-integration/providers.json" - context 'when signed in as an admin' do - before do - sign_in(admin) - end + context "when signed in as an admin" do + before { sign_in(admin) } - it 'should return the right response' do - get '/admin/plugins/chat-integration/providers.json' + it "should return the right response" do + get "/admin/plugins/chat-integration/providers.json" expect(response.status).to eq(200) json = response.parsed_body - expect(json['providers'].size).to eq(2) + expect(json["providers"].size).to eq(2) - expect(json['providers'].find { |h| h['name'] == 'dummy' }).to eq( - 'name' => 'dummy', - 'id' => 'dummy', - 'channel_parameters' => [] + expect(json["providers"].find { |h| h["name"] == "dummy" }).to eq( + "name" => "dummy", + "id" => "dummy", + "channel_parameters" => [], ) end end end - describe 'testing channels' do - include_examples 'admin constraints', 'get', '/admin/plugins/chat-integration/test.json' + describe "testing channels" do + include_examples "admin constraints", "get", "/admin/plugins/chat-integration/test.json" - context 'when signed in as an admin' do - before do - sign_in(admin) - end + context "when signed in as an admin" do + before { sign_in(admin) } - it 'should return the right response' do - post '/admin/plugins/chat-integration/test.json', params: { - channel_id: channel.id, topic_id: topic.id - } + it "should return the right response" do + post "/admin/plugins/chat-integration/test.json", + params: { + channel_id: channel.id, + topic_id: topic.id, + } expect(response.status).to eq(200) end - it 'should fail for invalid channel' do - post '/admin/plugins/chat-integration/test.json', params: { - channel_id: 999, topic_id: topic.id - } + it "should fail for invalid channel" do + post "/admin/plugins/chat-integration/test.json", + params: { + channel_id: 999, + topic_id: topic.id, + } expect(response.status).to eq(422) end end end - describe 'viewing channels' do - include_examples 'admin constraints', 'get', '/admin/plugins/chat-integration/channels.json' + describe "viewing channels" do + include_examples "admin constraints", "get", "/admin/plugins/chat-integration/channels.json" - context 'when signed in as an admin' do - before do - sign_in(admin) - end + context "when signed in as an admin" do + before { sign_in(admin) } - it 'should return the right response' do - rule = DiscourseChatIntegration::Rule.create( - channel: channel, - filter: 'follow', - category_id: category.id, - tags: [tag.name] - ) + it "should return the right response" do + rule = + DiscourseChatIntegration::Rule.create( + channel: channel, + filter: "follow", + category_id: category.id, + tags: [tag.name], + ) - get '/admin/plugins/chat-integration/channels.json', params: { provider: 'dummy' } + get "/admin/plugins/chat-integration/channels.json", params: { provider: "dummy" } expect(response.status).to eq(200) - channels = response.parsed_body['channels'] + channels = response.parsed_body["channels"] expect(channels.count).to eq(1) expect(channels.first).to eq( "id" => channel.id, - "provider" => 'dummy', - "data" => {}, + "provider" => "dummy", + "data" => { + }, "error_key" => nil, "error_info" => nil, - "rules" => [{ "id" => rule.id, "type" => 'normal', "group_name" => nil, "group_id" => nil, "filter" => "follow", "channel_id" => channel.id, "category_id" => category.id, "tags" => [tag.name] }] + "rules" => [ + { + "id" => rule.id, + "type" => "normal", + "group_name" => nil, + "group_id" => nil, + "filter" => "follow", + "channel_id" => channel.id, + "category_id" => category.id, + "tags" => [tag.name], + }, + ], ) end - it 'should fail for invalid provider' do - get '/admin/plugins/chat-integration/channels.json', params: { provider: 'someprovider' } + it "should fail for invalid provider" do + get "/admin/plugins/chat-integration/channels.json", params: { provider: "someprovider" } expect(response.status).to eq(400) end - end end - describe 'adding a channel' do - include_examples 'admin constraints', 'post', '/admin/plugins/chat-integration/channels.json' + describe "adding a channel" do + include_examples "admin constraints", "post", "/admin/plugins/chat-integration/channels.json" - context 'as an admin' do + context "as an admin" do + before { sign_in(admin) } - before do - sign_in(admin) - end - - it 'should be able to add a new channel' do - post '/admin/plugins/chat-integration/channels.json', params: { - channel: { - provider: 'dummy', - data: {} - } - } + it "should be able to add a new channel" do + post "/admin/plugins/chat-integration/channels.json", + params: { + channel: { + provider: "dummy", + data: { + }, + }, + } expect(response.status).to eq(200) channel = DiscourseChatIntegration::Channel.all.last - expect(channel.provider).to eq('dummy') + expect(channel.provider).to eq("dummy") end - it 'should fail for invalid params' do - post '/admin/plugins/chat-integration/channels.json', params: { - channel: { - provider: 'dummy2', - data: { val: 'something with whitespace' } - } - } + it "should fail for invalid params" do + post "/admin/plugins/chat-integration/channels.json", + params: { + channel: { + provider: "dummy2", + data: { + val: "something with whitespace", + }, + }, + } expect(response.status).to eq(422) end end end - describe 'updating a channel' do - let(:channel) { DiscourseChatIntegration::Channel.create(provider: 'dummy2', data: { val: "something" }) } + describe "updating a channel" do + let(:channel) do + DiscourseChatIntegration::Channel.create(provider: "dummy2", data: { val: "something" }) + end - include_examples 'admin constraints', 'put', "/admin/plugins/chat-integration/channels/1.json" + include_examples "admin constraints", "put", "/admin/plugins/chat-integration/channels/1.json" - context 'as an admin' do + context "as an admin" do + before { sign_in(admin) } - before do - sign_in(admin) - end - - it 'should be able update a channel' do - put "/admin/plugins/chat-integration/channels/#{channel.id}.json", params: { - channel: { - data: { val: "something-else" } - } - } + it "should be able update a channel" do + put "/admin/plugins/chat-integration/channels/#{channel.id}.json", + params: { + channel: { + data: { + val: "something-else", + }, + }, + } expect(response.status).to eq(200) @@ -191,30 +202,32 @@ describe 'Chat Controller', type: :request do expect(channel.data).to eq("val" => "something-else") end - it 'should fail for invalid params' do - put "/admin/plugins/chat-integration/channels/#{channel.id}.json", params: { - channel: { - data: { val: "something with whitespace" } - } - } + it "should fail for invalid params" do + put "/admin/plugins/chat-integration/channels/#{channel.id}.json", + params: { + channel: { + data: { + val: "something with whitespace", + }, + }, + } expect(response.status).to eq(422) end end end - describe 'deleting a channel' do - let(:channel) { DiscourseChatIntegration::Channel.create(provider: 'dummy', data: {}) } + describe "deleting a channel" do + let(:channel) { DiscourseChatIntegration::Channel.create(provider: "dummy", data: {}) } - include_examples 'admin constraints', 'delete', "/admin/plugins/chat-integration/channels/1.json" + include_examples "admin constraints", + "delete", + "/admin/plugins/chat-integration/channels/1.json" - context 'as an admin' do + context "as an admin" do + before { sign_in(admin) } - before do - sign_in(admin) - end - - it 'should be able delete a channel' do + it "should be able delete a channel" do delete "/admin/plugins/chat-integration/channels/#{channel.id}.json" expect(response.status).to eq(200) @@ -223,24 +236,22 @@ describe 'Chat Controller', type: :request do end end - describe 'adding a rule' do - include_examples 'admin constraints', 'put', '/admin/plugins/chat-integration/rules.json' + describe "adding a rule" do + include_examples "admin constraints", "put", "/admin/plugins/chat-integration/rules.json" - context 'as an admin' do + context "as an admin" do + before { sign_in(admin) } - before do - sign_in(admin) - end - - it 'should be able to add a new rule' do - post '/admin/plugins/chat-integration/rules.json', params: { - rule: { - channel_id: channel.id, - category_id: category.id, - filter: 'watch', - tags: [tag.name] - } - } + it "should be able to add a new rule" do + post "/admin/plugins/chat-integration/rules.json", + params: { + rule: { + channel_id: channel.id, + category_id: category.id, + filter: "watch", + tags: [tag.name], + }, + } expect(response.status).to eq(200) @@ -248,46 +259,51 @@ describe 'Chat Controller', type: :request do expect(rule.channel_id).to eq(channel.id) expect(rule.category_id).to eq(category.id) - expect(rule.filter).to eq('watch') + expect(rule.filter).to eq("watch") expect(rule.tags).to eq([tag.name]) - end - it 'should fail for invalid params' do - post '/admin/plugins/chat-integration/rules.json', params: { - rule: { - channel_id: channel.id, - category_id: category.id, - filter: 'watch', - tags: ['somenonexistanttag'] - } - } + it "should fail for invalid params" do + post "/admin/plugins/chat-integration/rules.json", + params: { + rule: { + channel_id: channel.id, + category_id: category.id, + filter: "watch", + tags: ["somenonexistanttag"], + }, + } expect(response.status).to eq(422) end end end - describe 'updating a rule' do - let(:rule) { DiscourseChatIntegration::Rule.create(channel: channel, filter: 'follow', category_id: category.id, tags: [tag.name]) } + describe "updating a rule" do + let(:rule) do + DiscourseChatIntegration::Rule.create( + channel: channel, + filter: "follow", + category_id: category.id, + tags: [tag.name], + ) + end - include_examples 'admin constraints', 'put', "/admin/plugins/chat-integration/rules/1.json" + include_examples "admin constraints", "put", "/admin/plugins/chat-integration/rules/1.json" - context 'as an admin' do + context "as an admin" do + before { sign_in(admin) } - before do - sign_in(admin) - end - - it 'should be able update a rule' do - put "/admin/plugins/chat-integration/rules/#{rule.id}.json", params: { - rule: { - channel_id: channel.id, - category_id: category2.id, - filter: rule.filter, - tags: rule.tags - } - } + it "should be able update a rule" do + put "/admin/plugins/chat-integration/rules/#{rule.id}.json", + params: { + rule: { + channel_id: channel.id, + category_id: category2.id, + filter: rule.filter, + tags: rule.tags, + }, + } expect(response.status).to eq(200) @@ -295,40 +311,38 @@ describe 'Chat Controller', type: :request do expect(rule.category_id).to eq(category2.id) end - it 'should fail for invalid params' do - put "/admin/plugins/chat-integration/rules/#{rule.id}.json", params: { - rule: { - channel_id: channel.id, - category_id: category.id, - filter: 'watch', - tags: ['somenonexistanttag'] - } - } + it "should fail for invalid params" do + put "/admin/plugins/chat-integration/rules/#{rule.id}.json", + params: { + rule: { + channel_id: channel.id, + category_id: category.id, + filter: "watch", + tags: ["somenonexistanttag"], + }, + } expect(response.status).to eq(422) end end end - describe 'deleting a rule' do + describe "deleting a rule" do let(:rule) do DiscourseChatIntegration::Rule.create!( channel_id: channel.id, - filter: 'follow', + filter: "follow", category_id: category.id, - tags: [tag.name] + tags: [tag.name], ) end - include_examples 'admin constraints', 'delete', "/admin/plugins/chat-integration/rules/1.json" + include_examples "admin constraints", "delete", "/admin/plugins/chat-integration/rules/1.json" - context 'as an admin' do + context "as an admin" do + before { sign_in(admin) } - before do - sign_in(admin) - end - - it 'should be able delete a rule' do + it "should be able delete a rule" do delete "/admin/plugins/chat-integration/rules/#{rule.id}.json" expect(response.status).to eq(200) @@ -336,5 +350,4 @@ describe 'Chat Controller', type: :request do end end end - end diff --git a/spec/requests/public_controller_spec.rb b/spec/requests/public_controller_spec.rb index f20e03a..668a273 100644 --- a/spec/requests/public_controller_spec.rb +++ b/spec/requests/public_controller_spec.rb @@ -1,16 +1,12 @@ # frozen_string_literal: true -require 'rails_helper' +require "rails_helper" -describe 'Public Controller', type: :request do +describe "Public Controller", type: :request do + before { SiteSetting.chat_integration_enabled = true } - before do - SiteSetting.chat_integration_enabled = true - end - - describe 'loading a transcript' do - - it 'should be able to load a transcript' do + describe "loading a transcript" do + it "should be able to load a transcript" do key = DiscourseChatIntegration::Helper.save_transcript("Some content here") get "/chat-transcript/#{key}.json" @@ -20,13 +16,11 @@ describe 'Public Controller', type: :request do expect(response.body).to eq('{"content":"Some content here"}') end - it 'should 404 for non-existant transcript' do - key = 'abcdefghijk' + it "should 404 for non-existant transcript" do + key = "abcdefghijk" get "/chat-transcript/#{key}.json" expect(response.status).to eq(404) end - end - end diff --git a/spec/services/manager_spec.rb b/spec/services/manager_spec.rb index 81885ad..c749cd9 100644 --- a/spec/services/manager_spec.rb +++ b/spec/services/manager_spec.rb @@ -1,11 +1,10 @@ # frozen_string_literal: true -require 'rails_helper' -require_dependency 'post_creator' -require_relative '../dummy_provider' +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) } @@ -14,31 +13,37 @@ RSpec.describe DiscourseChatIntegration::Manager do let(:first_post) { Fabricate(:post, topic: topic) } let(:second_post) { Fabricate(:post, topic: topic, post_number: 2) } - describe '.trigger_notifications' do + 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') } + let(:chan1) { DiscourseChatIntegration::Channel.create!(provider: "dummy") } + let(:chan2) { DiscourseChatIntegration::Channel.create!(provider: "dummy") } + let(:chan3) { DiscourseChatIntegration::Channel.create!(provider: "dummy") } - before do - SiteSetting.chat_integration_enabled = true - end + 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) + 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" }) + 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') + 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') + 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) @@ -48,17 +53,33 @@ RSpec.describe DiscourseChatIntegration::Manager do 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) + 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() + 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) + 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) @@ -66,9 +87,21 @@ RSpec.describe DiscourseChatIntegration::Manager do 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) + 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) @@ -76,7 +109,7 @@ RSpec.describe DiscourseChatIntegration::Manager do end it "should respect wildcard category settings" do - DiscourseChatIntegration::Rule.create!(channel: chan1, filter: 'watch', category_id: nil) + DiscourseChatIntegration::Rule.create!(channel: chan1, filter: "watch", category_id: nil) manager.trigger_notifications(first_post.id) @@ -84,17 +117,25 @@ RSpec.describe DiscourseChatIntegration::Manager do 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 + 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() + 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 + 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) @@ -102,8 +143,12 @@ RSpec.describe DiscourseChatIntegration::Manager do 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 + 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) @@ -111,18 +156,23 @@ RSpec.describe DiscourseChatIntegration::Manager do end it "should not notify about private messages" do - DiscourseChatIntegration::Rule.create!(channel: chan1, filter: 'follow', category_id: nil) # Wildcard watch + 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() + 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 + 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) @@ -133,8 +183,18 @@ RSpec.describe DiscourseChatIntegration::Manager do 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) + 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) @@ -146,40 +206,77 @@ RSpec.describe DiscourseChatIntegration::Manager do end it "should work for group mentions" do - third_post = Fabricate(:post, topic: topic, post_number: 3, raw: "let's mention @#{group.name}") + 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) + 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}") + 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 + 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() + 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 + 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) + 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) + 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}") + 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) @@ -187,15 +284,15 @@ RSpec.describe DiscourseChatIntegration::Manager do 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 + 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') + 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' + SiteSetting.chat_integration_discourse_username = "david" # Create a category category = Fabricate(:category, name: "Test category") @@ -208,7 +305,7 @@ RSpec.describe DiscourseChatIntegration::Manager do # Check no notification sent manager.trigger_notifications(first_post.id) - expect(provider.sent_to_channel_ids).to contain_exactly() + expect(provider.sent_to_channel_ids).to contain_exactly # Now expose category to new user category.set_permissions(Group[:friends] => :full) @@ -217,20 +314,17 @@ RSpec.describe DiscourseChatIntegration::Manager do # Check notification sent manager.trigger_notifications(first_post.id) expect(provider.sent_to_channel_ids).to contain_exactly(chan1.id) - end - describe 'with tags enabled' do - let(:tag) { Fabricate(:tag, name: 'gsoc') } + 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) do - SiteSetting.tagging_enabled = true - end + 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 + 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) @@ -238,16 +332,19 @@ RSpec.describe DiscourseChatIntegration::Manager do 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]) + 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 - end end - end