Remove webhooks and related code (#15827)

This commit is contained in:
Michael Kohl 2021-12-28 10:56:37 +07:00 committed by GitHub
parent ec9e9053d5
commit 06d6b68d9d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
42 changed files with 5 additions and 838 deletions

View file

@ -112,7 +112,6 @@ gem "stripe", "~> 5.42" # Ruby library for the Stripe API
gem "strong_migrations", "~> 0.7" # Catch unsafe migrations
gem "twitter", "~> 7.0" # A Ruby interface to the Twitter API
gem "uglifier", "~> 4.2" # Uglifier minifies JavaScript files
gem "ulid", "~> 1.3" # Universally Unique Lexicographically Sortable Identifier implementation for Ruby
gem "validate_url", "~> 1.0" # Library for validating urls in Rails
gem "vault", "~> 0.16" # Used to store secrets
gem "view_component", "~> 2.47" # View components for Rails

View file

@ -853,7 +853,6 @@ GEM
concurrent-ruby (~> 1.0)
uglifier (4.2.0)
execjs (>= 0.3.0, < 3)
ulid (1.3.0)
unf (0.1.4)
unf_ext
unf_ext (0.0.8)
@ -1059,7 +1058,6 @@ DEPENDENCIES
timecop (~> 0.9)
twitter (~> 7.0)
uglifier (~> 4.2)
ulid (~> 1.3)
validate_url (~> 1.0)
vault (~> 0.16)
vcr (~> 6.0)

View file

@ -1,11 +0,0 @@
module Admin
class WebhookEndpointsController < Admin::ApplicationController
layout "admin"
def index
@endpoints = Webhook::Endpoint.includes(:user)
.page(params[:page]).per(50)
.order(created_at: :desc)
end
end
end

View file

@ -1,44 +0,0 @@
module Api
module V0
class WebhooksController < ApiController
before_action :authenticate!
skip_before_action :verify_authenticity_token, only: %w[create destroy]
ATTRIBUTES_FOR_SERIALIZATION = %i[id user_id source target_url events created_at].freeze
private_constant :ATTRIBUTES_FOR_SERIALIZATION
def index
@webhooks = webhooks_scope.order(:id)
end
def create
@webhook = @user.webhook_endpoints.new(webhook_params)
@webhook.save!
render "show", status: :created
end
def show
@webhook = webhooks_scope.find(params[:id])
end
def destroy
webhook = webhooks_scope.find(params[:id])
webhook.destroy!
head :no_content
end
private
def webhooks_scope
@user.webhook_endpoints.select(ATTRIBUTES_FOR_SERIALIZATION)
end
def webhook_params
params.require(:webhook_endpoint).permit(:target_url, :source, events: [])
end
end
end
end

View file

@ -50,7 +50,6 @@ class AdminMenu
item(name: "developer tools", controller: "tools", children: [
item(name: "tools"),
item(name: "vault secrets", controller: "secrets"),
item(name: "webhooks", controller: "webhook_endpoints"),
item(name: "data update scripts", visible: false),
]),
]

View file

@ -111,7 +111,6 @@ class User < ApplicationRecord
foreign_key: :subscriber_id, inverse_of: :subscriber, dependent: :destroy
has_many :subscribers, through: :source_authored_user_subscriptions, dependent: :destroy
has_many :tweets, dependent: :nullify
has_many :webhook_endpoints, class_name: "Webhook::Endpoint", inverse_of: :user, dependent: :delete_all
has_many :devices, dependent: :delete_all
has_many :sponsorships, dependent: :delete_all

View file

@ -1,24 +0,0 @@
module Webhook
class Endpoint < ApplicationRecord
belongs_to :user, inverse_of: :webhook_endpoints
validates :events, presence: true
validates :source, presence: true
validates :target_url, presence: true, uniqueness: true, url: { schemes: %w[https] }
validates :user_id, presence: true
attribute :events, :string, array: true, default: []
scope :for_events, ->(events) { where("events @> ARRAY[?]::varchar[]", Array(events)) }
scope :for_app, ->(app_id) { where(oauth_application_id: app_id) }
def self.table_name_prefix
"webhook_"
end
def events=(events)
events = Array(events).map { |event| event.to_s.underscore }
super(Webhook::Event::EVENT_TYPES & events)
end
end
end

View file

@ -1,28 +0,0 @@
module Webhook
class Event
EVENT_TYPES = %w[
article_created
article_updated
article_destroyed
].freeze
attr_reader :event_type, :payload, :timestamp, :event_id
def initialize(event_type:, payload: {})
raise InvalidEvent unless EVENT_TYPES.include?(event_type)
@event_type = event_type
@payload = payload
now = Time.current
@timestamp = now.rfc3339
@event_id = Secrets::Generator.sortable(now)
end
def as_json(*_args)
Webhook::EventSerializer.new(self).serializable_hash
end
end
class InvalidEvent < StandardError; end
end

View file

@ -1,7 +0,0 @@
class WebhookEndpoint < ApplicationRecord
resourcify
# This class exists to take advantage of Rolify for limiting authorization
# on internal reports.
# NOTE: It is not backed by a database table and should not be expected to
# function like a traditional Rails model
end

View file

@ -1,6 +0,0 @@
module Webhook
class ArticleDestroyedSerializer < ApplicationSerializer
set_type :article
attributes :title
end
end

View file

@ -1,34 +0,0 @@
module Webhook
class ArticleSerializer < ApplicationSerializer
set_type :article
attributes :title, :description, :readable_publish_date, :cached_tag_list, :cached_tag_list_array,
:slug, :path, :url, :comments_count, :public_reactions_count, :body_markdown
attribute :canonical_url, &:processed_canonical_url
attribute :body_html, &:processed_html
attribute :created_at do |a|
a.created_at.utc.iso8601
end
attribute :edited_at do |a|
a.edited_at&.utc&.iso8601
end
attribute :crossposted_at do |a|
a.crossposted_at&.utc&.iso8601
end
attribute :published_at do |a|
a.published_at&.utc&.iso8601
end
attribute :last_comment_at do |a|
a.last_comment_at&.utc&.iso8601
end
attribute :cover_image do |a|
CloudCoverUrl.new(a.url).call
end
attribute :social_image do |article|
Articles::SocialImage.new(article).url
end
attribute :user do |a|
UserSerializer.new(a.user).serializable_hash
end
end
end

View file

@ -1,8 +0,0 @@
module Webhook
class EventSerializer < ApplicationSerializer
set_type :webhook_event
set_id :event_id
attributes :event_type, :timestamp, :payload
end
end

View file

@ -1,12 +0,0 @@
module Webhook
class UserSerializer < ApplicationSerializer
attributes :name, :username, :twitter_username, :github_username
attribute :website_url, &:processed_website_url
attribute :profile_image do |user|
Images::Profile.call(user.profile_image_url, length: 640)
end
attribute :profile_image_90 do |user|
Images::Profile.call(user.profile_image_url, length: 90)
end
end
end

View file

@ -1,9 +1,8 @@
module Articles
class Creator
def initialize(user, article_params, event_dispatcher = Webhook::DispatchEvent)
def initialize(user, article_params)
@user = user
@article_params = article_params
@event_dispatcher = event_dispatcher
end
def self.call(...)
@ -22,7 +21,6 @@ module Articles
# Send notifications to any mentioned users, followed by any users who follow the article's author.
Notification.send_to_mentioned_users_and_followers(article) if article.published?
dispatch_event(article)
end
article
@ -30,7 +28,7 @@ module Articles
private
attr_reader :user, :article_params, :event_dispatcher
attr_reader :user, :article_params
def rate_limit!
rate_limit_to_use = if user.decorate.considered_new?
@ -42,12 +40,6 @@ module Articles
user.rate_limiter.check_limit!(rate_limit_to_use)
end
def dispatch_event(article)
return unless article.published?
event_dispatcher.call("article_created", article)
end
def save_article
series = article_params[:series]
tags = article_params[:tags]

View file

@ -2,7 +2,7 @@ module Articles
module Destroyer
module_function
def call(article, event_dispatcher = Webhook::DispatchEvent)
def call(article)
# comments will automatically lose the connection to their article once `.destroy` is called,
# due to the `dependent: nullify` clause, so to remove their notifications,
# we need to cache the ids in advance
@ -15,8 +15,6 @@ module Articles
if article_comments_ids.present?
Notification.remove_all(notifiable_ids: article_comments_ids, notifiable_type: "Comment")
end
event_dispatcher.call("article_destroyed", article) if article.published?
end
end
end

View file

@ -2,11 +2,10 @@ module Articles
class Updater
Result = Struct.new(:success, :article, keyword_init: true)
def initialize(user, article, article_params, event_dispatcher = Webhook::DispatchEvent)
def initialize(user, article, article_params)
@user = user
@article = article
@article_params = article_params
@event_dispatcher = event_dispatcher
end
def self.call(...)
@ -16,9 +15,6 @@ module Articles
def call
user.rate_limiter.check_limit!(:article_update)
# Grab the state of the article's "publish" status before making any further updates to it.
was_previously_published = article.published
# updated edited time only if already published and not edited by an admin
update_edited_at = article.user == user && article.published
attrs = Articles::Attributes.new(article_params, article.user).for_update(update_edited_at: update_edited_at)
@ -52,19 +48,12 @@ module Articles
notifiable_type: "Mention")
end
end
# Do not notify if the article was previously already in a published state or is continually unpublished.
dispatch_event(article) if article.published || was_previously_published
end
Result.new(success: success, article: article.decorate)
end
private
attr_reader :user, :article, :article_params, :event_dispatcher
def dispatch_event(article)
event_dispatcher.call("article_updated", article)
end
attr_reader :user, :article, :article_params
end
end

View file

@ -1,10 +0,0 @@
module Secrets
module Generator
module_function
# Generates a unique and lexicographically sortable ID
def sortable(time = Time.current)
ULID.generate(time)
end
end
end

View file

@ -9,7 +9,6 @@ module Users
user.created_podcasts.update_all(creator_id: nil)
user.blocker_blocks.delete_all
user.blocked_blocks.delete_all
user.webhook_endpoints.delete_all
user.authored_notes.delete_all
user.display_ad_events.delete_all
user.email_messages.delete_all

View file

@ -1,26 +0,0 @@
module Webhook
class DispatchEvent
def initialize(event_type, record)
@event_type = event_type
@record = record
end
def self.call(...)
new(...).call
end
def call
endpoint_urls = Endpoint.for_events([event_type]).where(user_id: record.user_id).pluck(:target_url)
return if endpoint_urls.empty?
event_json = Event.new(event_type: event_type, payload: PayloadAdapter.new(record).hash).to_json
endpoint_urls.each do |url|
DispatchEventWorker.perform_async(url, event_json)
end
end
private
attr_reader :event_type, :record
end
end

View file

@ -1,30 +0,0 @@
module Webhook
class PayloadAdapter
def initialize(object)
raise InvalidPayloadObject unless object.is_a?(Article)
@object = object
end
def hash
serializer.new(prepared_object).serializable_hash
end
private
attr_reader :object
# decorate article before serializing
def prepared_object
return object unless object.is_a?(Article) && !object.decorated?
object.decorate
end
def serializer
object.destroyed? ? ArticleDestroyedSerializer : ArticleSerializer
end
end
class InvalidPayloadObject < StandardError; end
end

View file

@ -1,7 +0,0 @@
json.array! @webhooks.each do |endpoint|
json.type_of "webhook_endpoint"
json.extract!(endpoint, :id, :source, :target_url, :events)
json.created_at endpoint.created_at.rfc3339
end

View file

@ -1,7 +0,0 @@
json.type_of "webhook_endpoint"
json.call(@webhook, :id, :source, :target_url, :events)
json.created_at @webhook.created_at.rfc3339
json.partial! "api/v0/shared/user", user: @webhook.user

View file

@ -1,11 +0,0 @@
module Webhook
class DestroyWorker
include Sidekiq::Worker
sidekiq_options queue: :low_priority, retry: 10
def perform(user_id, application_id)
Webhook::Endpoint.destroy_by(user_id: user_id, oauth_application_id: application_id)
end
end
end

View file

@ -1,12 +0,0 @@
module Webhook
class DispatchEventWorker
include Sidekiq::Worker
sidekiq_options queue: :medium_priority
def perform(endpoint_url, event_json)
uri = Addressable::URI.parse(endpoint_url)
HTTParty.post(uri, headers: { "Content-Type" => "application/json" }, body: event_json, timeout: 10)
end
end
end

View file

@ -64,7 +64,6 @@ Rails.application.routes.draw do
get :organizations
end
resources :readinglist, only: [:index]
resources :webhooks, only: %i[index create show destroy]
resources :listings, only: %i[index show create update]
get "/listings/category/:category", to: "listings#index", as: :listings_category

View file

@ -143,7 +143,6 @@ namespace :admin do
post "bust_cache"
end
end
resources :webhook_endpoints, only: :index
# We do not expose the Data Update Scripts to all Forems by default.
constraints(->(_request) { FeatureFlag.enabled?(:data_update_scripts) }) do

View file

@ -1,8 +0,0 @@
FactoryBot.define do
factory :webhook_endpoint, class: "Webhook::Endpoint" do
target_url { Faker::Internet.url(scheme: "https") }
events { Webhook::Event::EVENT_TYPES }
user
source { "stackbit" }
end
end

View file

@ -110,7 +110,6 @@ RSpec.describe User, type: :model do
it { is_expected.to have_many(:subscribed_to_user_subscriptions).dependent(:destroy) }
it { is_expected.to have_many(:subscribers).dependent(:destroy) }
it { is_expected.to have_many(:tweets).dependent(:nullify) }
it { is_expected.to have_many(:webhook_endpoints).class_name("Webhook::Endpoint").dependent(:delete_all) }
# rubocop:disable RSpec/NamedSubject
it do

View file

@ -1,59 +0,0 @@
require "rails_helper"
RSpec.describe Webhook::Endpoint, type: :model do
let!(:endpoint) do
create(
:webhook_endpoint, user: user, events: %w[article_created article_updated article_destroyed]
)
end
let(:user) { create(:user) }
describe "validations" do
it { is_expected.to belong_to(:user).inverse_of(:webhook_endpoints) }
it { is_expected.to validate_presence_of(:events) }
it { is_expected.to validate_presence_of(:source) }
it { is_expected.to validate_presence_of(:target_url) }
it { is_expected.to validate_presence_of(:user_id) }
it { is_expected.to validate_uniqueness_of(:target_url) }
it { is_expected.to allow_value("https://foo.com").for(:target_url) }
it { is_expected.not_to allow_value("http://foo.com").for(:target_url) }
end
it "is valid" do
expect(endpoint).to be_valid
end
it "sets events according to the list" do
webhook = create(
:webhook_endpoint, events: %w[article_updated other_updated cool article_created]
)
webhook.reload
expect(webhook.events.sort).to eq(%w[article_created article_updated])
end
context "when endpoints exist" do
let!(:epoint2) { create(:webhook_endpoint, events: %w[article_destroyed], user: user) }
let!(:epoint3) { create(:webhook_endpoint, events: %w[article_updated article_destroyed]) }
before do
create(:webhook_endpoint, events: %w[article_created])
end
it "finds for events" do
d_points = described_class.for_events("article_destroyed")
expect(d_points.ids.sort).to eq([endpoint, epoint2, epoint3].map(&:id).sort)
end
it "finds for_events array" do
endpoints = described_class.for_events(%w[article_created article_destroyed])
expect(endpoints.ids).to eq([endpoint.id])
end
it "belongs to user" do
expect(user.webhook_endpoints.ids.sort).to eq([endpoint, epoint2].map(&:id).sort)
end
end
end

View file

@ -1,60 +0,0 @@
require "rails_helper"
RSpec.describe Webhook::Event, type: :model do
let(:article) { create(:article) }
let(:payload) { Webhook::PayloadAdapter.new(article).hash }
describe "validations" do
it "raises an exception with a unknown event type" do
expect do
described_class.new(event_type: "cool_event")
end.to raise_error(Webhook::InvalidEvent)
end
end
describe "#as_json" do
it "provides correct json (sample)" do
event = described_class.new(event_type: "article_created", payload: { title: "Hello, world" })
hash = event.as_json
attributes = hash[:data][:attributes]
expect(attributes[:event_type]).to eq("article_created")
expect(attributes[:timestamp]).to be_truthy
expect(attributes[:payload][:title]).to eq("Hello, world")
end
it "provides correct json including article" do
event = described_class.new(event_type: "article_updated", payload: payload)
hash = event.as_json
attributes = hash[:data][:attributes]
expect(attributes[:event_type]).to eq("article_updated")
expect(attributes[:payload][:data][:attributes][:title]).to eq(article.title)
end
it "provides an event_id dependent on time" do
event1 = described_class.new(event_type: "article_updated", payload: payload)
event1_id = event1.as_json.dig(:data, :id)
event2 = nil
Timecop.freeze(1.month.ago) do
event2 = described_class.new(event_type: "article_updated", payload: payload)
end
event2_id = event2.as_json.dig(:data, :id)
expect(event2_id < event1_id).to be(true)
end
end
describe "#to_json" do
it "provides correct json including article" do
event = described_class.new(event_type: "article_updated", payload: payload)
json = event.to_json
hash = JSON.parse(json)
attributes = hash["data"]["attributes"]
expect(attributes["event_type"]).to eq("article_updated")
expect(attributes["payload"]["data"]["attributes"]["title"]).to eq(article.title)
end
end
end

View file

@ -1,173 +0,0 @@
require "rails_helper"
RSpec.describe "Api::V0::Webhooks", type: :request do
let(:user) { create(:user) }
let!(:webhook) do
create(:webhook_endpoint, user: user, target_url: "https://api.example.com/go")
end
describe "GET /api/v0/webhooks" do
let(:webhook2) do
create(:webhook_endpoint, user: user, target_url: "https://api.example.com/webhook")
end
context "when accessing via cookie" do
before do
sign_in user
create(:webhook_endpoint)
end
it "returns 200 on success" do
get api_webhooks_path
expect(response).to have_http_status(:ok)
end
it "returns json on success" do
webhook2
get api_webhooks_path
expect(response.parsed_body).to include(
hash_including("id" => webhook.id, "target_url" => webhook.target_url),
hash_including("id" => webhook2.id, "target_url" => webhook2.target_url),
)
end
it "returns the correct json representation" do
get api_webhooks_path
expect(response.parsed_body.first).to eq(
"created_at" => webhook.created_at.rfc3339,
"events" => webhook.events,
"id" => webhook.id,
"source" => webhook.source,
"target_url" => webhook.target_url,
"type_of" => "webhook_endpoint",
)
end
end
end
describe "GET /api/v0/webhooks/:id" do
before do
sign_in user
end
it "returns 200 on success" do
get api_webhook_path(webhook.id)
expect(response).to have_http_status(:ok)
end
it "returns 404 if the webhook does not exist" do
get api_webhook_path(9999)
expect(response).to have_http_status(:not_found)
end
it "returns 404 if another user webhook is accessed" do
other_webhook = create(:webhook_endpoint, user: create(:user))
get api_webhook_path(other_webhook.id)
expect(response).to have_http_status(:not_found)
end
it "returns the correct json representation" do
get api_webhook_path(webhook.id)
expect(response.parsed_body).to include(
"created_at" => webhook.created_at.rfc3339,
"events" => webhook.events,
"id" => webhook.id,
"source" => webhook.source,
"target_url" => webhook.target_url,
"type_of" => "webhook_endpoint",
)
end
it "returns the correct json representation for the webhook user" do
get api_webhook_path(webhook.id)
response_webhook_user = response.parsed_body["user"]
expect(response_webhook_user).to eq({
"name" => webhook.user.name,
"username" => webhook.user.username,
"twitter_username" => webhook.user.twitter_username,
"github_username" => webhook.user.github_username,
"website_url" => webhook.user.processed_website_url,
"profile_image" => Images::Profile.call(webhook.user.profile_image_url,
length: 640),
"profile_image_90" => Images::Profile.call(webhook.user.profile_image_url,
length: 90)
})
end
end
describe "POST /api/v0/webhooks" do
let(:webhook_params) do
{
source: "stackbit",
target_url: Faker::Internet.url(scheme: "https"),
events: %w[article_created article_updated article_destroyed]
}
end
before do
sign_in user
end
it "creates a webhook" do
expect do
post api_webhooks_path, params: { webhook_endpoint: webhook_params }
end.to change(Webhook::Endpoint, :count).by(1)
end
it "creates a webhook with events and data" do
post api_webhooks_path, params: { webhook_endpoint: webhook_params }
created_webhook = user.webhook_endpoints.last
expect(created_webhook.events).to eq(%w[article_created article_updated article_destroyed])
expect(created_webhook.target_url).to eq(webhook_params[:target_url])
expect(created_webhook.source).to eq(webhook_params[:source])
expect(created_webhook.oauth_application_id).to eq(nil)
end
it "returns :created and json response on success" do
post api_webhooks_path, params: { webhook_endpoint: webhook_params }
expect(response).to have_http_status(:created)
expect(response.media_type).to eq("application/json")
json = JSON.parse(response.body)
expect(json["target_url"]).to eq(webhook_params[:target_url])
end
end
describe "DELETE /api/v0/webhooks/:id" do
before do
sign_in user
end
it "deletes the webhook" do
expect do
delete api_webhook_path(webhook.id)
end.to change(Webhook::Endpoint, :count).by(-1)
end
it "returns 204 on success" do
delete api_webhook_path(webhook.id)
expect(response).to have_http_status(:no_content)
end
it "doesn't allow to destroy other user webhook" do
other_webhook = create(:webhook_endpoint, user: create(:user))
expect do
delete api_webhook_path(other_webhook.id)
end.not_to change(Webhook::Endpoint, :count)
end
it "returns 404 if another user webhook is accessed" do
other_webhook = create(:webhook_endpoint, user: create(:user))
delete api_webhook_path(other_webhook.id)
expect(response).to have_http_status(:not_found)
end
end
end

View file

@ -77,24 +77,6 @@ RSpec.describe "ArticlesCreate", type: :request do
}
end
before do
create(:webhook_endpoint, events: %w[article_created article_updated], target_url: url, user: user)
end
it "doesn't schedule a dispatching event job (unpublished)" do
sidekiq_assert_no_enqueued_jobs(only: Webhook::DispatchEventWorker) do
post "/articles", params: article_params
end
end
it "schedules a dispatching event job (published)" do
body_markdown = "---\ntitle: hey hey hahuu\npublished: true\nseries: helloyo\n---\nYo ho ho#{rand(100)}"
article_params[:article][:body_markdown] = body_markdown
sidekiq_assert_enqueued_jobs(1, only: Webhook::DispatchEventWorker) do
post "/articles", params: article_params
end
end
it "doesn't fail when executing jobs" do
stub_request(:post, url).to_return(status: 200)
sidekiq_perform_enqueued_jobs do

View file

@ -167,13 +167,4 @@ RSpec.describe "ArticlesUpdate", type: :request do
expect(response).to redirect_to "#{article.path}/edit"
expect(article.reload.video_thumbnail_url).to include "https://i.imgur.com/HPiu7N4.jpg"
end
it "schedules a dispatching event job" do
create(:webhook_endpoint, events: %w[article_created article_updated], user: user)
sidekiq_assert_enqueued_jobs(1, only: Webhook::DispatchEventWorker) do
put "/articles/#{article.id}", params: {
article: { title: "new_title", body_markdown: "Yo ho ho#{rand(100)}", tag_list: "yo" }
}
end
end
end

View file

@ -38,21 +38,6 @@ RSpec.describe Articles::Creator, type: :service do
described_class.call(user, valid_attributes)
end.to change(NotificationSubscription, :count).by(1)
end
it "calls an event dispatcher" do
event_dispatcher = double
allow(event_dispatcher).to receive(:call)
article = described_class.call(user, valid_attributes, event_dispatcher)
expect(event_dispatcher).to have_received(:call).with("article_created", article)
end
it "doesn't call an event dispatcher when an article is unpublished" do
attributes = attributes_for(:article, published: false)
event_dispatcher = double
allow(event_dispatcher).to receive(:call)
article = described_class.call(user, attributes, event_dispatcher)
expect(event_dispatcher).not_to have_received(:call).with("article_created", article)
end
end
context "when invalid attributes" do
@ -94,12 +79,5 @@ RSpec.describe Articles::Creator, type: :service do
described_class.call(user, invalid_body_attributes)
end.not_to change(NotificationSubscription, :count)
end
it "doesn't call an event dispatcher" do
event_dispatcher = double
allow(event_dispatcher).to receive(:call)
described_class.call(user, invalid_body_attributes, event_dispatcher)
expect(event_dispatcher).not_to have_received(:call)
end
end
end

View file

@ -4,10 +4,6 @@ RSpec.describe Articles::Destroyer, type: :service do
let(:article) { create(:article) }
let(:event_dispatcher) { double }
before do
allow(event_dispatcher).to receive(:call)
end
it "destroys an article" do
described_class.call(article)
expect(Article.find_by(id: article.id)).to be_nil
@ -19,15 +15,4 @@ RSpec.describe Articles::Destroyer, type: :service do
described_class.call(article)
end
end
it "calls events dispatcher" do
described_class.call(article, event_dispatcher)
expect(event_dispatcher).to have_received(:call).with("article_destroyed", article)
end
it "doesn't call a dispatched when a draft is destroyed" do
draft = create(:article, published: false)
described_class.call(draft, event_dispatcher)
expect(event_dispatcher).not_to have_received(:call)
end
end

View file

@ -138,34 +138,4 @@ RSpec.describe Articles::Updater, type: :service do
end
end
end
describe "events dispatcher" do
let(:event_dispatcher) { double }
before do
allow(event_dispatcher).to receive(:call)
end
it "calls the dispatcher" do
described_class.call(user, article, attributes, event_dispatcher)
expect(event_dispatcher).to have_received(:call).with("article_updated", article)
end
it "doesn't call the dispatcher when unpublished => unpublished" do
described_class.call(user, draft, attributes, event_dispatcher)
expect(event_dispatcher).not_to have_received(:call)
end
it "calls the dispatcher when unpublished => published" do
attributes[:published] = true
described_class.call(user, draft, attributes, event_dispatcher)
expect(event_dispatcher).to have_received(:call).with("article_updated", draft)
end
it "calls the dispatcher when published => unpublished" do
attributes[:published] = false
described_class.call(user, article, attributes, event_dispatcher)
expect(event_dispatcher).to have_received(:call).with("article_updated", article)
end
end
end

View file

@ -1,18 +0,0 @@
require "rails_helper"
RSpec.describe Secrets::Generator, type: :service do
describe ".sortable" do
it "generates unique identifiers" do
expect(described_class.sortable).not_to eq(described_class.sortable)
end
it "generates sortable identifiers" do
now_id = described_class.sortable
a_month_from_now_id = described_class.sortable(1.month.from_now)
a_month_ago_id = described_class.sortable(1.month.ago)
expected_ids = [a_month_ago_id, now_id, a_month_from_now_id]
expect([now_id, a_month_from_now_id, a_month_ago_id].sort).to eq(expected_ids)
end
end
end

View file

@ -1,34 +0,0 @@
require "rails_helper"
RSpec.describe Webhook::DispatchEvent, type: :service do
let!(:user) { create(:user) }
let!(:article) { create(:article, user: user) }
it "does nothing if there are no corresponding endpoints" do
create(:webhook_endpoint, events: %w[article_created], user: user)
sidekiq_assert_no_enqueued_jobs(only: Webhook::DispatchEventWorker) do
described_class.call("article_destroyed", article)
end
end
it "schedules jobs" do
create(:webhook_endpoint, events: %w[article_created], user: user,
target_url: "https://create-webhooks.example.com/accept")
create(:webhook_endpoint, events: %w[article_created article_updated article_destroyed], user: user,
target_url: "https://all-webhooks.example.com/accept")
create(:webhook_endpoint, events: %w[article_destroyed], user: user,
target_url: "https://destroy-webhooks.example.com/accept")
sidekiq_assert_enqueued_jobs(2, only: Webhook::DispatchEventWorker) do
described_class.call("article_created", article)
end
end
it "doesn't schedule jobs if the endpoints belong to another user" do
user2 = create(:user)
create(:webhook_endpoint, events: %w[article_created], user: user2,
target_url: "https://create-webhooks.example.com/accept")
sidekiq_assert_no_enqueued_jobs(only: Webhook::DispatchEventWorker) do
described_class.call("article_created", article)
end
end
end

View file

@ -1,35 +0,0 @@
require "rails_helper"
RSpec.describe Webhook::PayloadAdapter, type: :service do
it "raises an exception when invalid object is passed" do
expect do
described_class.new(User.new).hash
end.to raise_error(Webhook::InvalidPayloadObject)
end
describe "#hash" do
let(:user) { create(:user) }
let!(:article) { create(:article, title: "I'm super", user: user) }
it "returns a hash for a persisted article" do
data = described_class.new(article).hash
expect(data).to be_kind_of(Hash)
expect(data[:data][:attributes][:title]).to eq(article.title)
expect(data[:data][:attributes][:body_markdown]).to be_truthy
end
it "returns a hash with a user" do
data = described_class.new(article).hash
expect(data[:data][:attributes][:user][:data][:attributes][:username]).to eq(user.username)
end
it "returns a hash for a destroyed article" do
article = create(:article, title: "hello")
article.destroy
data = described_class.new(article).hash
expect(data).to be_kind_of(Hash)
expect(data[:data][:attributes][:title]).to eq("hello")
expect(data[:data][:attributes][:body_markdown]).to be_falsey
end
end
end

View file

@ -1,16 +0,0 @@
require "rails_helper"
RSpec.describe Webhook::DestroyWorker, type: :worker do
let(:user) { create(:user) }
let!(:webhook_endpoint) { create(:webhook_endpoint, user: user) }
let!(:other_webhook_endpoint) { create(:webhook_endpoint) }
let(:worker) { subject }
describe "#perform_now" do
xit "destroys webhook by user_id and app_id" do
worker.perform(user.id)
expect(Webhook::Endpoint.find_by(id: webhook_endpoint.id)).to be_nil
expect(other_webhook_endpoint.reload).to be_present
end
end
end

View file

@ -1,29 +0,0 @@
require "rails_helper"
RSpec.describe Webhook::DispatchEventWorker, type: :worker do
let(:worker) { subject }
include_examples "#enqueues_on_correct_queue", "medium_priority", ["https://example.com", {}.to_json]
describe "#perform_now" do
let(:article) { create(:article) }
let(:payload) { Webhook::PayloadAdapter.new(article).hash }
let(:json) { Webhook::Event.new(event_type: "article_updated", payload: payload).to_json }
let(:url) { Faker::Internet.url }
let(:client) { HTTParty }
it "posts an event" do
allow(client).to receive(:post)
worker.perform(url, json)
expect(client).to have_received(:post).once
.with(Addressable::URI.parse(url), headers: { "Content-Type" => "application/json" },
body: json,
timeout: 10)
end
it "doesn't fail" do
stub_request(:post, url).to_return(status: 200)
worker.perform(url, json)
end
end
end

Binary file not shown.