Add Slack messengers: part 2 (#6916)
* Add Slack::Messengers::ArticlePublished * Add Slack::Messengers::ReactionVomit * Add Slack::Messengers::CommentUserWarned * Add missing URL helpers * Add Slack::Messengers::ArticleFetchedFeed * Remove callback condition * Build, don't create
This commit is contained in:
parent
d6e5808dec
commit
f2b0be2043
24 changed files with 516 additions and 159 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
39
app/services/slack/messengers/article_fetched_feed.rb
Normal file
39
app/services/slack/messengers/article_fetched_feed.rb
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
module Slack
|
||||
module Messengers
|
||||
class ArticleFetchedFeed
|
||||
MESSAGE_TEMPLATE = <<~TEXT.chomp.freeze
|
||||
New Article Retrieved via RSS: %<title>s
|
||||
%<url>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
|
||||
39
app/services/slack/messengers/article_published.rb
Normal file
39
app/services/slack/messengers/article_published.rb
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
module Slack
|
||||
module Messengers
|
||||
class ArticlePublished
|
||||
MESSAGE_TEMPLATE = <<~TEXT.chomp.freeze
|
||||
New Article Published: %<title>s
|
||||
%<url>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
|
||||
48
app/services/slack/messengers/comment_user_warned.rb
Normal file
48
app/services/slack/messengers/comment_user_warned.rb
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
module Slack
|
||||
module Messengers
|
||||
class CommentUserWarned
|
||||
MESSAGE_TEMPLATE = <<~TEXT.chomp.freeze
|
||||
Activity: %<url>s
|
||||
Comment text: %<text>s
|
||||
---
|
||||
Manage commenter - @%<username>s: %<internal_user_url>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
|
||||
43
app/services/slack/messengers/reaction_vomit.rb
Normal file
43
app/services/slack/messengers/reaction_vomit.rb
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
module Slack
|
||||
module Messengers
|
||||
class ReactionVomit
|
||||
MESSAGE_TEMPLATE = <<~TEXT.chomp.freeze
|
||||
%<name>s (%<user_url>s)
|
||||
reacted with a vomit on
|
||||
%<reactable_url>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
|
||||
|
|
@ -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) }
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
45
spec/services/slack/messengers/article_fetched_feed_spec.rb
Normal file
45
spec/services/slack/messengers/article_fetched_feed_spec.rb
Normal file
|
|
@ -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
|
||||
55
spec/services/slack/messengers/article_published_spec.rb
Normal file
55
spec/services/slack/messengers/article_published_spec.rb
Normal file
|
|
@ -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
|
||||
51
spec/services/slack/messengers/comment_user_warned_spec.rb
Normal file
51
spec/services/slack/messengers/comment_user_warned_spec.rb
Normal file
|
|
@ -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
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 } }
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
42
spec/services/slack/messengers/reaction_vomit_spec.rb
Normal file
42
spec/services/slack/messengers/reaction_vomit_spec.rb
Normal file
|
|
@ -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
|
||||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue