diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 14f9158e9..617ef9649 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -182,14 +182,22 @@ module ApplicationHelper
URL.url(uri)
end
- def tag_url(tag, page)
- URL.tag(tag, page)
- end
-
def article_url(article)
URL.article(article)
end
+ def comment_url(comment)
+ URL.comment(comment)
+ end
+
+ def reaction_url(reaction)
+ URL.reaction(reaction)
+ end
+
+ def tag_url(tag, page)
+ URL.tag(tag, page)
+ end
+
def user_url(user)
URL.user(user)
end
diff --git a/app/lib/url.rb b/app/lib/url.rb
index 188cd2fd0..b8aa00a53 100644
--- a/app/lib/url.rb
+++ b/app/lib/url.rb
@@ -30,7 +30,23 @@ module URL
url(article.path)
end
- # Creates an article URL
+ # Creates a comment URL
+ #
+ # @param comment [Comment] the comment to create the URL for
+ def self.comment(comment)
+ url(comment.path)
+ end
+
+ # Creates a reaction URL
+ #
+ # A reaction URL is the URL of its reactable.
+ #
+ # @param reactable [Reaction] the reaction to create the URL for
+ def self.reaction(reaction)
+ url(reaction.reactable.path)
+ end
+
+ # Creates a tag URL
#
# @param tag [Tag] the tag to create the URL for
def self.tag(tag, page = 1)
diff --git a/app/models/article.rb b/app/models/article.rb
index a00eef547..c03fc4e2b 100644
--- a/app/models/article.rb
+++ b/app/models/article.rb
@@ -72,7 +72,7 @@ class Article < ApplicationRecord
before_save :update_cached_user
after_save :bust_cache, :detect_human_language
- after_save :notify_slack_channel_about_publication, if: -> { published && published_at > 30.seconds.ago }
+ after_save :notify_slack_channel_about_publication
after_update_commit :update_notifications, if: proc { |article| article.notifications.any? && !article.saved_changes.empty? }
after_update_commit :update_reading_list_reactions, if: proc { |article|
@@ -718,18 +718,6 @@ class Article < ApplicationRecord
end
def notify_slack_channel_about_publication
- url = "#{ApplicationConfig['APP_PROTOCOL']}#{ApplicationConfig['APP_DOMAIN']}"
-
- message = <<~MESSAGE.chomp
- New Article Published: #{title}
- #{url}#{path}
- MESSAGE
-
- SlackBotPingWorker.perform_async(
- message: message,
- channel: "activity",
- username: "article_bot",
- icon_emoji: ":writing_hand:",
- )
+ Slack::Messengers::ArticlePublished.call(article: self)
end
end
diff --git a/app/models/comment.rb b/app/models/comment.rb
index 2bff7c912..04af345d2 100644
--- a/app/models/comment.rb
+++ b/app/models/comment.rb
@@ -27,7 +27,7 @@ class Comment < ApplicationRecord
validates :commentable_type, inclusion: { in: %w[Article PodcastEpisode] }
validates :user_id, presence: true
- after_create :notify_slack_channel_about_warned_users, if: -> { user.warned }
+ after_create :notify_slack_channel_about_warned_users
after_create :after_create_checks
after_create_commit :record_field_test_event
after_commit :calculate_score
@@ -248,20 +248,6 @@ class Comment < ApplicationRecord
end
def notify_slack_channel_about_warned_users
- url = "#{ApplicationConfig['APP_PROTOCOL']}#{ApplicationConfig['APP_DOMAIN']}"
-
- message = <<~MESSAGE.chomp
- Activity: #{url}#{path}
- Comment text: #{body_markdown.truncate(300)}
- ---
- Manage commenter - @#{user.username}: #{url}/internal/users/#{user.id}
- MESSAGE
-
- SlackBotPingWorker.perform_async(
- message: message,
- channel: "warned-user-comments",
- username: "sloan_watch_bot",
- icon_emoji: ":sloan:",
- )
+ Slack::Messengers::CommentUserWarned.call(comment: self)
end
end
diff --git a/app/models/reaction.rb b/app/models/reaction.rb
index 122f27660..513a29e08 100644
--- a/app/models/reaction.rb
+++ b/app/models/reaction.rb
@@ -216,19 +216,6 @@ class Reaction < ApplicationRecord
end
def notify_slack_channel_about_vomit_reaction
- url = "#{ApplicationConfig['APP_PROTOCOL']}#{ApplicationConfig['APP_DOMAIN']}"
-
- message = <<~MESSAGE.chomp
- #{user.name} (#{url}#{user.path})
- reacted with a #{category} on
- #{url}#{reactable.path}
- MESSAGE
-
- SlackBotPingWorker.perform_async(
- message: message,
- channel: "abuse-reports",
- username: "abuse_bot",
- icon_emoji: ":cry:",
- )
+ Slack::Messengers::ReactionVomit.call(reaction: self)
end
end
diff --git a/app/services/rss_reader.rb b/app/services/rss_reader.rb
index 59796ddae..8c35aabb5 100644
--- a/app/services/rss_reader.rb
+++ b/app/services/rss_reader.rb
@@ -79,7 +79,7 @@ class RssReader
organization_id: nil,
)
- send_slack_notification(article)
+ Slack::Messengers::ArticleFetchedFeed.call(article: article)
end
def get_host_without_www(url)
@@ -108,17 +108,6 @@ class RssReader
relation.where(title: title).or(relation.where(feed_source_url: feed_source_url)).exists?
end
- def send_slack_notification(article)
- return unless Rails.env.production?
-
- SlackBotPingWorker.perform_async(
- message: "New Article Retrieved via RSS: #{article.title}\nhttps://dev.to#{article.path}",
- channel: "activity",
- username: "article_bot",
- icon_emoji: ":robot_face:",
- )
- end
-
def log_error(error_msg, metadata)
Rails.logger.error(error_msg, metadata)
end
diff --git a/app/services/slack/messengers/article_fetched_feed.rb b/app/services/slack/messengers/article_fetched_feed.rb
new file mode 100644
index 000000000..a2dc85dab
--- /dev/null
+++ b/app/services/slack/messengers/article_fetched_feed.rb
@@ -0,0 +1,39 @@
+module Slack
+ module Messengers
+ class ArticleFetchedFeed
+ MESSAGE_TEMPLATE = <<~TEXT.chomp.freeze
+ New Article Retrieved via RSS: %
s
+ %s
+ TEXT
+
+ def initialize(article:)
+ @article = article
+ end
+
+ def self.call(*args)
+ new(*args).call
+ end
+
+ def call
+ return unless article.published_from_feed?
+
+ message = format(
+ MESSAGE_TEMPLATE,
+ title: article.title,
+ url: URL.article(article),
+ )
+
+ SlackBotPingWorker.perform_async(
+ message: message,
+ channel: "activity",
+ username: "article_bot",
+ icon_emoji: ":robot_face:",
+ )
+ end
+
+ private
+
+ attr_reader :article
+ end
+ end
+end
diff --git a/app/services/slack/messengers/article_published.rb b/app/services/slack/messengers/article_published.rb
new file mode 100644
index 000000000..dc97631b2
--- /dev/null
+++ b/app/services/slack/messengers/article_published.rb
@@ -0,0 +1,39 @@
+module Slack
+ module Messengers
+ class ArticlePublished
+ MESSAGE_TEMPLATE = <<~TEXT.chomp.freeze
+ New Article Published: %s
+ %s
+ TEXT
+
+ def initialize(article:)
+ @article = article
+ end
+
+ def self.call(*args)
+ new(*args).call
+ end
+
+ def call
+ return unless article.published && article.published_at > 30.seconds.ago
+
+ message = format(
+ MESSAGE_TEMPLATE,
+ title: article.title,
+ url: URL.article(article),
+ )
+
+ SlackBotPingWorker.perform_async(
+ message: message,
+ channel: "activity",
+ username: "article_bot",
+ icon_emoji: ":writing_hand:",
+ )
+ end
+
+ private
+
+ attr_reader :article
+ end
+ end
+end
diff --git a/app/services/slack/messengers/comment_user_warned.rb b/app/services/slack/messengers/comment_user_warned.rb
new file mode 100644
index 000000000..66b4faf00
--- /dev/null
+++ b/app/services/slack/messengers/comment_user_warned.rb
@@ -0,0 +1,48 @@
+module Slack
+ module Messengers
+ class CommentUserWarned
+ MESSAGE_TEMPLATE = <<~TEXT.chomp.freeze
+ Activity: %s
+ Comment text: %s
+ ---
+ Manage commenter - @%s: %s
+ TEXT
+
+ def initialize(comment:)
+ @comment = comment
+ @user = comment.user
+ end
+
+ def self.call(*args)
+ new(*args).call
+ end
+
+ def call
+ return unless user.warned
+
+ internal_user_url = URL.url(
+ Rails.application.routes.url_helpers.internal_user_path(user),
+ )
+
+ message = format(
+ MESSAGE_TEMPLATE,
+ url: URL.comment(comment),
+ text: comment.body_markdown.truncate(300),
+ username: user.username,
+ internal_user_url: internal_user_url,
+ )
+
+ SlackBotPingWorker.perform_async(
+ message: message,
+ channel: "warned-user-comments",
+ username: "sloan_watch_bot",
+ icon_emoji: ":sloan:",
+ )
+ end
+
+ private
+
+ attr_reader :comment, :user
+ end
+ end
+end
diff --git a/app/services/slack/messengers/reaction_vomit.rb b/app/services/slack/messengers/reaction_vomit.rb
new file mode 100644
index 000000000..e77e68f4f
--- /dev/null
+++ b/app/services/slack/messengers/reaction_vomit.rb
@@ -0,0 +1,43 @@
+module Slack
+ module Messengers
+ class ReactionVomit
+ MESSAGE_TEMPLATE = <<~TEXT.chomp.freeze
+ %s (%s)
+ reacted with a vomit on
+ %s
+ TEXT
+
+ def initialize(reaction:)
+ @reaction = reaction
+ end
+
+ def self.call(*args)
+ new(*args).call
+ end
+
+ def call
+ return unless reaction.category == "vomit"
+
+ user = reaction.user
+
+ message = format(
+ MESSAGE_TEMPLATE,
+ name: user.name,
+ user_url: URL.user(user),
+ reactable_url: URL.reaction(reaction),
+ )
+
+ SlackBotPingWorker.perform_async(
+ message: message,
+ channel: "abuse-reports",
+ username: "abuse_bot",
+ icon_emoji: ":cry:",
+ )
+ end
+
+ private
+
+ attr_reader :reaction
+ end
+ end
+end
diff --git a/spec/lib/url_spec.rb b/spec/lib/url_spec.rb
index b2007c871..6e5e75487 100644
--- a/spec/lib/url_spec.rb
+++ b/spec/lib/url_spec.rb
@@ -45,6 +45,28 @@ RSpec.describe URL, type: :lib do
end
end
+ describe ".comment" do
+ let(:comment) { build(:comment) }
+
+ it "returns the correct URL for a comment" do
+ expect(described_class.comment(comment)).to eq("https://dev.to#{comment.path}")
+ end
+ end
+
+ describe ".reaction" do
+ it "returns the correct URL for an article's reaction" do
+ article = build(:article, path: "/username1/slug")
+ reaction = build(:reaction, reactable: article)
+ expect(described_class.reaction(reaction)).to eq("https://dev.to#{article.path}")
+ end
+
+ it "returns the correct URL for a comment's reaction" do
+ comment = build(:comment)
+ reaction = build(:reaction, reactable: comment)
+ expect(described_class.reaction(reaction)).to eq("https://dev.to#{comment.path}")
+ end
+ end
+
describe ".user" do
let(:user) { build(:user) }
diff --git a/spec/models/article_spec.rb b/spec/models/article_spec.rb
index 375c6624c..4abf178d5 100644
--- a/spec/models/article_spec.rb
+++ b/spec/models/article_spec.rb
@@ -793,46 +793,33 @@ RSpec.describe Article, type: :model do
end
end
- describe "slack notifications" do
+ describe "slack messages" do
before do
# making sure there are no other enqueued jobs from other tests
sidekiq_perform_enqueued_jobs(only: SlackBotPingWorker)
end
- it "notifies the proper slack channel about a recently published new article" do
- Timecop.freeze(Time.current) do
- article = create(:article, published: true)
-
- url = "#{ApplicationConfig['APP_PROTOCOL']}#{ApplicationConfig['APP_DOMAIN']}"
- message = "New Article Published: #{article.title}\n#{url}#{article.path}"
- args = {
- message: message,
- channel: "activity",
- username: "article_bot",
- icon_emoji: ":writing_hand:"
- }.stringify_keys
-
- sidekiq_assert_enqueued_jobs(1, only: SlackBotPingWorker)
- job = sidekiq_enqueued_jobs(worker: SlackBotPingWorker).last
- expect(job["args"]).to eq([args])
+ it "queues a slack message to be sent" do
+ sidekiq_assert_enqueued_jobs(1, only: SlackBotPingWorker) do
+ article.update(published: true, published_at: Time.current)
end
end
- it "does not send a notification for a new article published more than 30 seconds ago" do
+ it "does not queue a message for an article published more than 30 seconds ago" do
Timecop.freeze(Time.current) do
sidekiq_assert_no_enqueued_jobs(only: SlackBotPingWorker) do
- create(:article, published: true, published_at: 31.seconds.ago)
+ article.update(published: true, published_at: 31.seconds.ago)
end
end
end
- it "does not send a notification for a non published article" do
+ it "does not queue a message for a draft article" do
sidekiq_assert_no_enqueued_jobs(only: SlackBotPingWorker) do
- create(:article, published: false)
+ article.update(body_markdown: "foobar", published: false)
end
end
- it "sends a notification for a draft article that gets published" do
+ it "queues a message for a draft article that gets published" do
Timecop.freeze(Time.current) do
sidekiq_assert_enqueued_with(job: SlackBotPingWorker) do
article.update_columns(published: false)
diff --git a/spec/models/comment_spec.rb b/spec/models/comment_spec.rb
index 00d59c9ab..f1c7fb4da 100644
--- a/spec/models/comment_spec.rb
+++ b/spec/models/comment_spec.rb
@@ -312,35 +312,20 @@ RSpec.describe Comment, type: :model do
expect { comment.save }.to change(user, :last_comment_at)
end
- describe "slack notifications" do
+ describe "slack messages" do
+ let!(:user) { create(:user) }
+
before do
# making sure there are no other enqueued jobs from other tests
sidekiq_perform_enqueued_jobs(only: SlackBotPingWorker)
end
- it "notifies proper slack channel when a warned user leaves a comment" do
- user = create(:user)
+ it "queues a slack message when a warned user leaves a comment" do
user.add_role(:warned)
- comment = create(:comment, user: user, commentable: article)
- url = "#{ApplicationConfig['APP_PROTOCOL']}#{ApplicationConfig['APP_DOMAIN']}"
- message = <<~MESSAGE.chomp
- Activity: #{url}#{comment.path}
- Comment text: #{comment.body_markdown.truncate(300)}
- ---
- Manage commenter - @#{user.username}: #{url}/internal/users/#{user.id}
- MESSAGE
-
- args = {
- message: message,
- channel: "warned-user-comments",
- username: "sloan_watch_bot",
- icon_emoji: ":sloan:"
- }.stringify_keys
-
- sidekiq_assert_enqueued_jobs(1, only: SlackBotPingWorker)
- job = sidekiq_enqueued_jobs(worker: SlackBotPingWorker).last
- expect(job["args"]).to eq([args])
+ sidekiq_assert_enqueued_jobs(1, only: SlackBotPingWorker) do
+ create(:comment, user: user, commentable: article)
+ end
end
it "does not send notification if a regular user leaves a comment" do
diff --git a/spec/models/reaction_spec.rb b/spec/models/reaction_spec.rb
index edf7e3669..a25709391 100644
--- a/spec/models/reaction_spec.rb
+++ b/spec/models/reaction_spec.rb
@@ -181,7 +181,7 @@ RSpec.describe Reaction, type: :model do
end
context "when callbacks are called after create" do
- describe "slack notifications" do
+ describe "slack messages" do
let_it_be_changeable(:user) { create(:user, :trusted) }
let_it_be_readonly(:article) { create(:article, user: user) }
@@ -190,28 +190,19 @@ RSpec.describe Reaction, type: :model do
sidekiq_perform_enqueued_jobs(only: SlackBotPingWorker)
end
- it "notifies proper slack channel about vomit reaction" do
- url = "#{ApplicationConfig['APP_PROTOCOL']}#{ApplicationConfig['APP_DOMAIN']}"
- message = "#{user.name} (#{url}#{user.path})\nreacted with a vomit on\n#{url}#{article.path}"
- args = {
- message: message,
- channel: "abuse-reports",
- username: "abuse_bot",
- icon_emoji: ":cry:"
- }.stringify_keys
-
- sidekiq_assert_enqueued_with(job: SlackBotPingWorker, args: [args]) do
+ it "queues a slack message to be sent for a vomit reaction" do
+ sidekiq_assert_enqueued_jobs(1, only: SlackBotPingWorker) do
create(:reaction, reactable: article, user: user, category: "vomit")
end
end
- it "does not send notification for like reaction" do
+ it "does not queue a message for a like reaction" do
sidekiq_assert_no_enqueued_jobs(only: SlackBotPingWorker) do
create(:reaction, reactable: article, user: user, category: "like")
end
end
- it "does not send notification for thumbsdown reaction" do
+ it "does not queue a message for a thumbsdown reaction" do
sidekiq_assert_no_enqueued_jobs(only: SlackBotPingWorker) do
create(:reaction, reactable: article, user: user, category: "thumbsdown")
end
diff --git a/spec/services/rss_reader_spec.rb b/spec/services/rss_reader_spec.rb
index e359298d4..86d403bdc 100644
--- a/spec/services/rss_reader_spec.rb
+++ b/spec/services/rss_reader_spec.rb
@@ -1,18 +1,14 @@
require "rails_helper"
require "rss"
-vcr_option = {
- cassette_name: "rss_feeds",
- allow_playback_repeats: "true"
-}
-
default_logger = Rails.logger
-RSpec.describe RssReader, type: :service, vcr: vcr_option do
+RSpec.describe RssReader, type: :service, vcr: VCR_OPTIONS[:rss_feeds] do
let(:link) { "https://medium.com/feed/@vaidehijoshi" }
let(:nonmedium_link) { "https://circleci.com/blog/feed.xml" }
let(:nonpermanent_link) { "https://medium.com/feed/@macsiri/" }
let(:rss_data) { RSS::Parser.parse(HTTParty.get(link).body, false) }
+ let!(:rss_reader) { described_class.new }
# Override the default Rails logger as these tests require the Timber logger.
before do
@@ -30,66 +26,91 @@ RSpec.describe RssReader, type: :service, vcr: vcr_option do
end
it "fetch only articles from an feed_url" do
- described_class.get_all_articles
+ rss_reader.get_all_articles
+
# the result within the approval file depends on the feed
# not fetching comments is baked into this
verify(format: :txt) { Article.count }
end
it "does not re-create article if it already exist" do
- described_class.new.get_all_articles
- expect { described_class.new.get_all_articles }.not_to change(Article, :count)
+ rss_reader.get_all_articles
+
+ expect { rss_reader.get_all_articles }.not_to change(Article, :count)
end
it "parses correctly" do
- described_class.new.get_all_articles
+ rss_reader.get_all_articles
+
verify format: :txt do
User.find_by(feed_url: nonpermanent_link).articles.first.body_markdown
end
end
- it "sets time current" do
- described_class.new.get_all_articles
- expect(User.find_by(feed_url: nonpermanent_link).feed_fetched_at).to be > 2.minutes.ago
+ it "sets feed_fetched_at to the current time" do
+ Timecop.freeze(Time.current) do
+ rss_reader.get_all_articles
+
+ user = User.find_by(feed_url: nonpermanent_link)
+ feed_fetched_at = user.feed_fetched_at
+ expect(feed_fetched_at.to_i).to eq(Time.current.to_i)
+ end
end
it "does refetch same user over and over by default" do
user = User.find_by(feed_url: nonpermanent_link)
+
Timecop.freeze(Time.current) do
- user.update_column(:feed_fetched_at, Time.current)
+ user.update_columns(feed_fetched_at: Time.current)
+
fetched_at_time = user.reload.feed_fetched_at
+
# travel a few seconds in the future to simulate a new time
3.times do |i|
- Timecop.travel((i + 5).seconds.from_now) { described_class.new.get_all_articles }
+ Timecop.travel((i + 5).seconds.from_now) do
+ rss_reader.get_all_articles
+ end
end
+
expect(user.reload.feed_fetched_at > fetched_at_time).to be(true)
end
end
it "logs an article creation error" do
- reader = described_class.new
- allow(reader).to receive(:make_from_rss_item).and_raise(StandardError)
+ allow(rss_reader).to receive(:make_from_rss_item).and_raise(StandardError)
allow(Rails.logger).to receive(:error)
- reader.get_all_articles
+
+ rss_reader.get_all_articles
+
expect(Rails.logger).to have_received(:error).at_least(:once)
end
it "logs a fetching error" do
- reader = described_class.new
- allow(reader).to receive(:fetch_rss).and_raise(StandardError)
+ allow(rss_reader).to receive(:fetch_rss).and_raise(StandardError)
allow(Rails.logger).to receive(:error)
- reader.get_all_articles
+
+ rss_reader.get_all_articles
+
expect(Rails.logger).to have_received(:error).at_least(:once)
end
+
+ it "queues as many slack messages as there are articles" do
+ expect do
+ rss_reader.get_all_articles
+ end.to change(SlackBotPingWorker.jobs, :count).by(12)
+ end
end
context "when feed_referential_link is false" do
it "does not self-reference links for user" do
# Article.find_by is used by find_and_replace_possible_links!
- # checking it's invocation s a shortcut to testing the functionality.
+ # checking its invocation is a shortcut to testing the functionality.
allow(Article).to receive(:find_by).and_call_original
+
create(:user, feed_url: nonpermanent_link, feed_referential_link: false)
- described_class.get_all_articles
+
+ rss_reader.get_all_articles
+
expect(Article).not_to have_received(:find_by)
end
end
@@ -102,41 +123,52 @@ RSpec.describe RssReader, type: :service, vcr: vcr_option do
end
it "gets articles for user" do
+ rss_reader.fetch_user(User.find_by(feed_url: link))
+
# the result within the approval file depends on the feed
- described_class.new.fetch_user(User.first)
verify(format: :txt) { Article.count }
end
it "does not set featured_number" do
- described_class.new.fetch_user(User.first)
- expect(Article.all.map(&:featured_number).uniq).to eq([nil])
+ user = User.find_by(feed_url: link)
+ rss_reader.fetch_user(user)
+
+ expect(user.articles.select(&:featured_number)).to be_empty
end
it "logs an article creation error on the standard logger" do
- reader = described_class.new
- allow(reader).to receive(:make_from_rss_item).and_raise(StandardError)
+ allow(rss_reader).to receive(:make_from_rss_item).and_raise(StandardError)
allow(Rails.logger).to receive(:error)
- reader.fetch_user(User.first)
+
+ rss_reader.fetch_user(User.find_by(feed_url: link))
+
expect(Rails.logger).to have_received(:error).at_least(:once)
end
it "logs a fetching error on the standard logger" do
- reader = described_class.new
- allow(reader).to receive(:fetch_rss).and_raise(StandardError)
+ allow(rss_reader).to receive(:fetch_rss).and_raise(StandardError)
allow(Rails.logger).to receive(:error)
- reader.fetch_user(User.first)
+
+ rss_reader.fetch_user(User.find_by(feed_url: link))
+
expect(Rails.logger).to have_received(:error).at_least(:once)
end
+
+ it "queues as many slack messages as there are user articles" do
+ expect do
+ rss_reader.fetch_user(User.find_by(feed_url: link))
+ end.to change(SlackBotPingWorker.jobs, :count).by(1)
+ end
end
describe "#valid_feed_url?" do
it "returns true on valid feed url" do
- expect(described_class.new.valid_feed_url?(link)).to be true
+ expect(rss_reader.valid_feed_url?(link)).to be(true)
end
it "returns false on invalid feed url" do
bad_link = "www.google.com"
- expect(described_class.new.valid_feed_url?(bad_link)).to be false
+ expect(rss_reader.valid_feed_url?(bad_link)).to be(false)
end
end
end
diff --git a/spec/services/slack/messengers/article_fetched_feed_spec.rb b/spec/services/slack/messengers/article_fetched_feed_spec.rb
new file mode 100644
index 000000000..758b5365d
--- /dev/null
+++ b/spec/services/slack/messengers/article_fetched_feed_spec.rb
@@ -0,0 +1,45 @@
+require "rails_helper"
+
+RSpec.describe Slack::Messengers::ArticleFetchedFeed, type: :service do
+ let!(:article) do
+ build(:article).tap do |article|
+ article.title = "Awesome article"
+ article.published_from_feed = true
+ end
+ end
+
+ let(:default_params) { { article: article } }
+
+ it "does not message slack for a new article not coming from the feed" do
+ sidekiq_assert_no_enqueued_jobs(only: SlackBotPingWorker) do
+ article.published_from_feed = false
+
+ described_class.call(article: article)
+ end
+ end
+
+ it "contains the correct info", :aggregate_failures do
+ sidekiq_assert_enqueued_jobs(1, only: SlackBotPingWorker) do
+ described_class.call(default_params)
+ end
+
+ job = sidekiq_enqueued_jobs(worker: SlackBotPingWorker).last
+ message = job["args"].first["message"]
+
+ expect(message).to include(article.title)
+ expect(message).to include(URL.article(article))
+ end
+
+ it "messages the proper channel with the proper username and emoji", :aggregate_failures do
+ sidekiq_assert_enqueued_jobs(1, only: SlackBotPingWorker) do
+ described_class.call(default_params)
+ end
+
+ job = sidekiq_enqueued_jobs(worker: SlackBotPingWorker).last
+ job_args = job["args"].first
+
+ expect(job_args["channel"]).to eq("activity")
+ expect(job_args["username"]).to eq("article_bot")
+ expect(job_args["icon_emoji"]).to eq(":robot_face:")
+ end
+end
diff --git a/spec/services/slack/messengers/article_published_spec.rb b/spec/services/slack/messengers/article_published_spec.rb
new file mode 100644
index 000000000..6c74f5c74
--- /dev/null
+++ b/spec/services/slack/messengers/article_published_spec.rb
@@ -0,0 +1,55 @@
+require "rails_helper"
+
+RSpec.describe Slack::Messengers::ArticlePublished, type: :service do
+ let(:article) do
+ build(:article).tap do |article|
+ article.title = "Awesome article"
+ article.published = true
+ article.published_at = Time.current
+ end
+ end
+
+ let(:default_params) { { article: article } }
+
+ it "does not message slack for a draft article" do
+ sidekiq_assert_no_enqueued_jobs(only: SlackBotPingWorker) do
+ article = build(:article, published: false)
+ described_class.call(article: article)
+ end
+ end
+
+ it "does not message slack for an article that was published long ago" do
+ sidekiq_assert_no_enqueued_jobs(only: SlackBotPingWorker) do
+ article = build(:article).tap do |art|
+ art.published = true
+ art.published_at = 1.minute.ago
+ end
+ described_class.call(article: article)
+ end
+ end
+
+ it "contains the correct info", :aggregate_failures do
+ sidekiq_assert_enqueued_jobs(1, only: SlackBotPingWorker) do
+ described_class.call(default_params)
+ end
+
+ job = sidekiq_enqueued_jobs(worker: SlackBotPingWorker).last
+ message = job["args"].first["message"]
+
+ expect(message).to include(article.title)
+ expect(message).to include(URL.article(article))
+ end
+
+ it "messages the proper channel with the proper username and emoji", :aggregate_failures do
+ sidekiq_assert_enqueued_jobs(1, only: SlackBotPingWorker) do
+ described_class.call(default_params)
+ end
+
+ job = sidekiq_enqueued_jobs(worker: SlackBotPingWorker).last
+ job_args = job["args"].first
+
+ expect(job_args["channel"]).to eq("activity")
+ expect(job_args["username"]).to eq("article_bot")
+ expect(job_args["icon_emoji"]).to eq(":writing_hand:")
+ end
+end
diff --git a/spec/services/slack/messengers/comment_user_warned_spec.rb b/spec/services/slack/messengers/comment_user_warned_spec.rb
new file mode 100644
index 000000000..94e120fff
--- /dev/null
+++ b/spec/services/slack/messengers/comment_user_warned_spec.rb
@@ -0,0 +1,51 @@
+require "rails_helper"
+
+RSpec.describe Slack::Messengers::CommentUserWarned, type: :service do
+ let(:comment) { create(:comment) }
+ let(:user) { comment.user }
+
+ let(:default_params) { { comment: comment } }
+
+ it "does not message slack for a comment with a regular user" do
+ sidekiq_assert_no_enqueued_jobs(only: SlackBotPingWorker) do
+ described_class.call(comment: build(:comment, user: build(:user)))
+ end
+ end
+
+ context "when the uesr has been warned" do
+ before do
+ user.add_role(:warned)
+ end
+
+ it "contains the correct info", :aggregate_failures do
+ sidekiq_assert_enqueued_jobs(1, only: SlackBotPingWorker) do
+ described_class.call(default_params)
+ end
+
+ job = sidekiq_enqueued_jobs(worker: SlackBotPingWorker).last
+ message = job["args"].first["message"]
+
+ internal_user_url = URL.url(
+ Rails.application.routes.url_helpers.internal_user_path(user),
+ )
+
+ expect(message).to include(URL.comment(comment))
+ expect(message).to include(comment.body_markdown.truncate(300))
+ expect(message).to include(user.username)
+ expect(message).to include(internal_user_url)
+ end
+
+ it "messages the proper channel with the proper username and emoji", :aggregate_failures do
+ sidekiq_assert_enqueued_jobs(1, only: SlackBotPingWorker) do
+ described_class.call(default_params)
+ end
+
+ job = sidekiq_enqueued_jobs(worker: SlackBotPingWorker).last
+ job_args = job["args"].first
+
+ expect(job_args["channel"]).to eq("warned-user-comments")
+ expect(job_args["username"]).to eq("sloan_watch_bot")
+ expect(job_args["icon_emoji"]).to eq(":sloan:")
+ end
+ end
+end
diff --git a/spec/services/slack/messengers/feedback_spec.rb b/spec/services/slack/messengers/feedback_spec.rb
index 2f28fd981..377f1b3e5 100644
--- a/spec/services/slack/messengers/feedback_spec.rb
+++ b/spec/services/slack/messengers/feedback_spec.rb
@@ -1,7 +1,7 @@
require "rails_helper"
RSpec.describe Slack::Messengers::Feedback, type: :service do
- let_it_be_readonly(:user) { create(:user) }
+ let(:user) { build(:user) }
let(:default_params) do
{
type: "abuse-reports",
diff --git a/spec/services/slack/messengers/potential_spammer_spec.rb b/spec/services/slack/messengers/potential_spammer_spec.rb
index a90556542..c70f4f677 100644
--- a/spec/services/slack/messengers/potential_spammer_spec.rb
+++ b/spec/services/slack/messengers/potential_spammer_spec.rb
@@ -1,7 +1,7 @@
require "rails_helper"
RSpec.describe Slack::Messengers::PotentialSpammer, type: :service do
- let_it_be_readonly(:user) { create(:user) }
+ let(:user) { build(:user) }
let(:default_params) { { user: user } }
diff --git a/spec/services/slack/messengers/rate_limit_spec.rb b/spec/services/slack/messengers/rate_limit_spec.rb
index 426177a52..7496220c6 100644
--- a/spec/services/slack/messengers/rate_limit_spec.rb
+++ b/spec/services/slack/messengers/rate_limit_spec.rb
@@ -1,7 +1,7 @@
require "rails_helper"
RSpec.describe Slack::Messengers::RateLimit, type: :service do
- let_it_be_readonly(:user) { create(:user) }
+ let(:user) { build(:user) }
let(:default_params) do
{
diff --git a/spec/services/slack/messengers/reaction_vomit_spec.rb b/spec/services/slack/messengers/reaction_vomit_spec.rb
new file mode 100644
index 000000000..63f8a932c
--- /dev/null
+++ b/spec/services/slack/messengers/reaction_vomit_spec.rb
@@ -0,0 +1,42 @@
+require "rails_helper"
+
+RSpec.describe Slack::Messengers::ReactionVomit, type: :service do
+ let(:reaction) { build(:reaction, category: :vomit, user: build(:user)) }
+ let(:user) { reaction.user }
+
+ let(:default_params) { { reaction: reaction } }
+
+ it "does not message slack for a like reaction" do
+ sidekiq_assert_no_enqueued_jobs(only: SlackBotPingWorker) do
+ reaction = build(:reaction, category: :like)
+ described_class.call(reaction: reaction)
+ end
+ end
+
+ it "contains the correct info", :aggregate_failures do
+ sidekiq_assert_enqueued_jobs(1, only: SlackBotPingWorker) do
+ described_class.call(default_params)
+ end
+
+ job = sidekiq_enqueued_jobs(worker: SlackBotPingWorker).last
+ message = job["args"].first["message"]
+
+ expect(message).to include(user.name)
+ expect(message).to include(URL.user(user))
+ expect(message).to include(reaction.category)
+ expect(message).to include(URL.reaction(reaction))
+ end
+
+ it "messages the proper channel with the proper username and emoji", :aggregate_failures do
+ sidekiq_assert_enqueued_jobs(1, only: SlackBotPingWorker) do
+ described_class.call(default_params)
+ end
+
+ job = sidekiq_enqueued_jobs(worker: SlackBotPingWorker).last
+ job_args = job["args"].first
+
+ expect(job_args["channel"]).to eq("abuse-reports")
+ expect(job_args["username"]).to eq("abuse_bot")
+ expect(job_args["icon_emoji"]).to eq(":cry:")
+ end
+end
diff --git a/spec/services/slack/messengers/sponsorship_spec.rb b/spec/services/slack/messengers/sponsorship_spec.rb
index 4836d341e..46068ccda 100644
--- a/spec/services/slack/messengers/sponsorship_spec.rb
+++ b/spec/services/slack/messengers/sponsorship_spec.rb
@@ -1,9 +1,9 @@
require "rails_helper"
RSpec.describe Slack::Messengers::Sponsorship, type: :service do
- let_it_be_readonly(:user) { create(:user) }
- let_it_be_readonly(:organization) { create(:organization) }
- let(:tag) { create(:tag) }
+ let(:user) { build(:user) }
+ let(:organization) { build(:organization) }
+ let(:tag) { build(:tag) }
let(:default_params) do
{
diff --git a/spec/support/initializers/vcr.rb b/spec/support/initializers/vcr.rb
index c59b013eb..4e0f63459 100644
--- a/spec/support/initializers/vcr.rb
+++ b/spec/support/initializers/vcr.rb
@@ -29,5 +29,9 @@ VCR_OPTIONS = {
twitter_fetch_status: {
cassette_name: "twitter_fetch_status",
allow_playback_repeats: true
+ },
+ rss_feeds: {
+ cassette_name: "rss_feeds",
+ allow_playback_repeats: true
}
}.freeze