FIX: Payload for webhooks should be current as of the time the event was triggered.

https://meta.discourse.org/t/group-category-tag-user-deleted-webhooks-not-firing/87752
This commit is contained in:
Guo Xiang Tan 2018-05-21 16:23:09 +08:00
parent cba3942850
commit bf84037f79
6 changed files with 288 additions and 181 deletions

View File

@ -2,8 +2,14 @@ require 'excon'
module Jobs module Jobs
class EmitWebHookEvent < Jobs::Base class EmitWebHookEvent < Jobs::Base
PING_EVENT = 'ping'.freeze
def execute(args) def execute(args)
[:web_hook_id, :event_type].each do |key| %i{
web_hook_id
event_type
payload
}.each do |key|
raise Discourse::InvalidParameters.new(key) unless args[key].present? raise Discourse::InvalidParameters.new(key) unless args[key].present?
end end
@ -19,8 +25,7 @@ module Jobs
return if web_hook.category_ids.present? && (!args[:category_id].present? || return if web_hook.category_ids.present? && (!args[:category_id].present? ||
!web_hook.category_ids.include?(args[:category_id])) !web_hook.category_ids.include?(args[:category_id]))
event_type = args[:event_type].to_s args[:payload] = JSON.parse(args[:payload])
return unless self.send("setup_#{event_type}", args)
end end
web_hook_request(args, web_hook) web_hook_request(args, web_hook)
@ -32,50 +37,8 @@ module Jobs
Guardian.new(Discourse.system_user) Guardian.new(Discourse.system_user)
end end
def setup_post(args)
post = Post.with_deleted.find_by(id: args[:post_id])
return if post.blank?
args[:payload] = WebHookPostSerializer.new(post, scope: guardian, root: false).as_json
end
def setup_topic(args)
topic_view = TopicView.new(args[:topic_id], Discourse.system_user)
return if topic_view.blank?
args[:payload] = WebHookTopicViewSerializer.new(topic_view, scope: guardian, root: false).as_json
end
def setup_user(args)
user = User.find_by(id: args[:user_id])
return if user.blank?
args[:payload] = WebHookUserSerializer.new(user, scope: guardian, root: false).as_json
end
def setup_group(args)
group = Group.find_by(id: args[:group_id])
return if group.blank?
args[:payload] = WebHookGroupSerializer.new(group, scope: guardian, root: false).as_json
end
def setup_category(args)
category = Category.find_by(id: args[:category_id])
return if category.blank?
args[:payload] = WebHookCategorySerializer.new(category, scope: guardian, root: false).as_json
end
def setup_tag(args)
tag = Tag.find_by(id: args[:tag_id])
return if tag.blank?
args[:payload] = TagSerializer.new(tag, scope: guardian, root: false).as_json
end
def setup_flag(args)
flag = PostAction.find_by(id: args[:flag_id])
return if flag.blank?
args[:payload] = WebHookFlagSerializer.new(flag, scope: guardian, root: false).as_json
end
def ping_event?(event_type) def ping_event?(event_type)
event_type.to_s == 'ping'.freeze PING_EVENT == event_type.to_s
end end
def build_web_hook_body(args, web_hook) def build_web_hook_body(args, web_hook)
@ -89,7 +52,6 @@ module Jobs
end end
new_body = Plugin::Filter.apply(:after_build_web_hook_body, self, body) new_body = Plugin::Filter.apply(:after_build_web_hook_body, self, body)
MultiJson.dump(new_body) MultiJson.dump(new_body)
end end
@ -120,7 +82,7 @@ module Jobs
'Content-Length' => body.bytesize, 'Content-Length' => body.bytesize,
'Content-Type' => content_type, 'Content-Type' => content_type,
'Host' => uri.host, 'Host' => uri.host,
'User-Agent' => "Discourse/" + Discourse::VERSION::STRING, 'User-Agent' => "Discourse/#{Discourse::VERSION::STRING}",
'X-Discourse-Instance' => Discourse.base_url, 'X-Discourse-Instance' => Discourse.base_url,
'X-Discourse-Event-Id' => web_hook_event.id, 'X-Discourse-Event-Id' => web_hook_event.id,
'X-Discourse-Event-Type' => args[:event_type] 'X-Discourse-Event-Type' => args[:event_type]
@ -129,7 +91,7 @@ module Jobs
headers['X-Discourse-Event'] = args[:event_name].to_s if args[:event_name].present? headers['X-Discourse-Event'] = args[:event_name].to_s if args[:event_name].present?
if web_hook.secret.present? if web_hook.secret.present?
headers['X-Discourse-Event-Signature'] = "sha256=" + OpenSSL::HMAC.hexdigest("sha256", web_hook.secret, body) headers['X-Discourse-Event-Signature'] = "sha256=#{OpenSSL::HMAC.hexdigest("sha256", web_hook.secret, body)}"
end end
now = Time.zone.now now = Time.zone.now

View File

@ -30,30 +30,79 @@ class WebHook < ActiveRecord::Base
[WebHookEventType.find(WebHookEventType::POST)] [WebHookEventType.find(WebHookEventType::POST)]
end end
def self.find_by_type(type) def strip_url
self.payload_url = (payload_url || "").strip.presence
end
def self.active_web_hooks(type)
WebHook.where(active: true) WebHook.where(active: true)
.joins(:web_hook_event_types) .joins(:web_hook_event_types)
.where("web_hooks.wildcard_web_hook = ? OR web_hook_event_types.name = ?", true, type.to_s) .where("web_hooks.wildcard_web_hook = ? OR web_hook_event_types.name = ?", true, type.to_s)
.uniq .uniq
end end
def self.enqueue_hooks(type, opts = {}) def self.enqueue_hooks(type, opts = {}, web_hooks = nil)
find_by_type(type).each do |w| (web_hooks || active_web_hooks(type)).each do |web_hook|
Jobs.enqueue(:emit_web_hook_event, opts.merge(web_hook_id: w.id, event_type: type.to_s)) Jobs.enqueue(:emit_web_hook_event, opts.merge(
web_hook_id: web_hook.id, event_type: type.to_s
))
end end
end end
def self.enqueue_topic_hooks(event, topic, user = nil) def self.enqueue_object_hooks(type, object, event, serializer = nil)
WebHook.enqueue_hooks(:topic, topic_id: topic.id, category_id: topic&.category_id, event_name: event.to_s) Scheduler::Defer.later("Enqueue User Webhook") do
web_hooks = active_web_hooks(type)
return if web_hooks.empty?
serializer ||= "WebHook#{type.capitalize}Serializer".constantize
WebHook.enqueue_hooks(type, {
event_name: event.to_s,
payload: serializer.new(object,
scope: self.guardian,
root: false
).to_json
}, web_hooks)
end
end end
def self.enqueue_post_hooks(event, post, user = nil) def self.enqueue_topic_hooks(event, topic)
WebHook.enqueue_hooks(:post, post_id: post.id, category_id: post&.topic&.category_id, event_name: event.to_s) Scheduler::Defer.later("Enqueue Topic Webhook") do
web_hooks = active_web_hooks('topic')
return if web_hooks.empty?
topic_view = TopicView.new(topic.id, Discourse.system_user)
WebHook.enqueue_hooks(:topic, {
category_id: topic&.category_id,
event_name: event.to_s,
payload: WebHookTopicViewSerializer.new(topic_view,
scope: self.guardian,
root: false
).to_json
}, web_hooks)
end
end end
def strip_url def self.enqueue_post_hooks(event, post)
self.payload_url = (payload_url || "").strip.presence Scheduler::Defer.later("Enqueue Post Webhook") do
web_hooks = active_web_hooks('post')
return if web_hooks.empty?
WebHook.enqueue_hooks(:post, {
category_id: post&.topic&.category_id,
event_name: event.to_s,
payload: WebHookPostSerializer.new(post,
scope: self.guardian,
root: false
).to_json
}, web_hooks)
end
end end
private
def self.guardian
@guardian ||= Guardian.new(Discourse.system_user)
end
end end
# == Schema Information # == Schema Information

View File

@ -1,6 +1,9 @@
%i(topic_destroyed topic_recovered).each do |event| %i(
DiscourseEvent.on(event) do |topic, user| topic_destroyed
WebHook.enqueue_topic_hooks(event, topic, user) topic_recovered
).each do |event|
DiscourseEvent.on(event) do |topic, _|
WebHook.enqueue_topic_hooks(event, topic)
end end
end end
@ -8,16 +11,17 @@ DiscourseEvent.on(:topic_status_updated) do |topic, status|
WebHook.enqueue_topic_hooks("topic_#{status}_status_updated", topic) WebHook.enqueue_topic_hooks("topic_#{status}_status_updated", topic)
end end
DiscourseEvent.on(:topic_created) do |topic, _, user| DiscourseEvent.on(:topic_created) do |topic, _, _|
WebHook.enqueue_topic_hooks(:topic_created, topic, user) WebHook.enqueue_topic_hooks(:topic_created, topic)
end end
%i(post_created %i(
post_destroyed post_created
post_recovered).each do |event| post_destroyed
post_recovered
DiscourseEvent.on(event) do |post, _, user| ).each do |event|
WebHook.enqueue_post_hooks(event, post, user) DiscourseEvent.on(event) do |post, _, _|
WebHook.enqueue_post_hooks(event, post)
end end
end end
@ -39,7 +43,7 @@ end
user_updated user_updated
).each do |event| ).each do |event|
DiscourseEvent.on(event) do |user| DiscourseEvent.on(event) do |user|
WebHook.enqueue_hooks(:user, user_id: user.id, event_name: event.to_s) WebHook.enqueue_object_hooks(:user, user, event)
end end
end end
@ -49,7 +53,7 @@ end
group_destroyed group_destroyed
).each do |event| ).each do |event|
DiscourseEvent.on(event) do |group| DiscourseEvent.on(event) do |group|
WebHook.enqueue_hooks(:group, group_id: group.id, event_name: event.to_s) WebHook.enqueue_object_hooks(:group, group, event)
end end
end end
@ -59,7 +63,7 @@ end
category_destroyed category_destroyed
).each do |event| ).each do |event|
DiscourseEvent.on(event) do |category| DiscourseEvent.on(event) do |category|
WebHook.enqueue_hooks(:category, category_id: category.id, event_name: event.to_s) WebHook.enqueue_object_hooks(:category, category, event)
end end
end end
@ -69,7 +73,7 @@ end
tag_destroyed tag_destroyed
).each do |event| ).each do |event|
DiscourseEvent.on(event) do |tag| DiscourseEvent.on(event) do |tag|
WebHook.enqueue_hooks(:tag, tag_id: tag.id, event_name: event.to_s) WebHook.enqueue_object_hooks(:tag, tag, event)
end end
end end
@ -80,6 +84,6 @@ end
flag_deferred flag_deferred
).each do |event| ).each do |event|
DiscourseEvent.on(event) do |flag| DiscourseEvent.on(event) do |flag|
WebHook.enqueue_hooks(:flag, flag_id: flag.id, event_name: event.to_s) WebHook.enqueue_object_hooks(:flag, flag, event, WebHookFlagSerializer)
end end
end end

View File

@ -7,21 +7,41 @@ describe Jobs::EmitWebHookEvent do
let(:user) { Fabricate(:user) } let(:user) { Fabricate(:user) }
it 'raises an error when there is no web hook record' do it 'raises an error when there is no web hook record' do
expect { subject.execute(event_type: 'post') }.to raise_error(Discourse::InvalidParameters) expect do
subject.execute(event_type: 'post', payload: {})
end.to raise_error(Discourse::InvalidParameters)
end end
it 'raises an error when there is no event type' do it 'raises an error when there is no event type' do
expect { subject.execute(web_hook_id: 1) }.to raise_error(Discourse::InvalidParameters) expect do
subject.execute(web_hook_id: 1, payload: {})
end.to raise_error(Discourse::InvalidParameters)
end
it 'raises an error when there is no payload' do
expect do
subject.execute(web_hook_id: 1, event_type: 'post')
end.to raise_error(Discourse::InvalidParameters)
end end
it "doesn't emit when the hook is inactive" do it "doesn't emit when the hook is inactive" do
Jobs::EmitWebHookEvent.any_instance.expects(:web_hook_request).never subject.execute(
subject.execute(web_hook_id: inactive_hook.id, event_type: 'post', post_id: post.id) web_hook_id: inactive_hook.id,
event_type: 'post',
payload: { test: "some payload" }.to_json
)
end end
it 'emits normally with sufficient arguments' do it 'emits normally with sufficient arguments' do
Jobs::EmitWebHookEvent.any_instance.expects(:web_hook_request).once stub_request(:post, "https://meta.discourse.org/webhook_listener")
subject.execute(web_hook_id: post_hook.id, event_type: 'post', post_id: post.id) .with(body: "{\"post\":{\"test\":\"some payload\"}}")
.to_return(body: 'OK', status: 200)
subject.execute(
web_hook_id: post_hook.id,
event_type: 'post',
payload: { test: "some payload" }.to_json
)
end end
context 'with category filters' do context 'with category filters' do
@ -31,69 +51,64 @@ describe Jobs::EmitWebHookEvent do
let(:topic_hook) { Fabricate(:topic_web_hook, categories: [category]) } let(:topic_hook) { Fabricate(:topic_web_hook, categories: [category]) }
it "doesn't emit when event is not related with defined categories" do it "doesn't emit when event is not related with defined categories" do
Jobs::EmitWebHookEvent.any_instance.expects(:web_hook_request).never subject.execute(
web_hook_id: topic_hook.id,
subject.execute(web_hook_id: topic_hook.id, event_type: 'topic',
event_type: 'topic', category_id: topic.category.id,
topic_id: topic.id, payload: { test: "some payload" }.to_json
user_id: user.id, )
category_id: topic.category.id)
end end
it 'emit when event is related with defined categories' do it 'emit when event is related with defined categories' do
Jobs::EmitWebHookEvent.any_instance.expects(:web_hook_request).once stub_request(:post, "https://meta.discourse.org/webhook_listener")
.with(body: "{\"topic\":{\"test\":\"some payload\"}}")
.to_return(body: 'OK', status: 200)
subject.execute(web_hook_id: topic_hook.id, subject.execute(
event_type: 'topic', web_hook_id: topic_hook.id,
topic_id: topic_with_category.id, event_type: 'topic',
user_id: user.id, category_id: topic_with_category.category.id,
category_id: topic_with_category.category.id) payload: { test: "some payload" }.to_json
)
end end
end end
describe '.web_hook_request' do describe '#web_hook_request' do
it 'creates delivery event record' do it 'creates delivery event record' do
stub_request(:post, "https://meta.discourse.org/webhook_listener") stub_request(:post, "https://meta.discourse.org/webhook_listener")
.to_return(body: 'OK', status: 200) .to_return(body: 'OK', status: 200)
WebHookEventType.all.pluck(:name).each do |name| WebHookEventType.all.pluck(:name).each do |name|
web_hook_id = Fabricate("#{name}_web_hook").id web_hook_id = Fabricate("#{name}_web_hook").id
object_id = Fabricate(name).id
expect do expect do
subject.execute(web_hook_id: web_hook_id, event_type: name, "#{name}_id": object_id) subject.execute(
web_hook_id: web_hook_id,
event_type: name,
payload: { test: "some payload" }.to_json
)
end.to change(WebHookEvent, :count).by(1) end.to change(WebHookEvent, :count).by(1)
end end
end end
it 'skips silently on missing post' do
expect do
subject.execute(web_hook_id: post_hook.id, event_type: 'post', post_id: (Post.maximum(:id).to_i + 1))
end.not_to raise_error
end
it 'should not skip trashed post' do
stub_request(:post, "https://meta.discourse.org/webhook_listener")
.to_return(body: 'OK', status: 200)
expect do
post.trash!
subject.execute(web_hook_id: post_hook.id, event_type: 'post', post_id: post.id)
end.to change(WebHookEvent, :count).by(1)
end
it 'sets up proper request headers' do it 'sets up proper request headers' do
stub_request(:post, "https://meta.discourse.org/webhook_listener") stub_request(:post, "https://meta.discourse.org/webhook_listener")
.to_return(headers: { test: 'string' }, body: 'OK', status: 200) .to_return(headers: { test: 'string' }, body: 'OK', status: 200)
subject.execute(web_hook_id: post_hook.id, event_type: 'ping', event_name: 'ping') subject.execute(
web_hook_id: post_hook.id,
event_type: described_class::PING_EVENT,
event_name: described_class::PING_EVENT,
payload: { test: "some payload" }.to_json
)
event = WebHookEvent.last event = WebHookEvent.last
headers = MultiJson.load(event.headers) headers = MultiJson.load(event.headers)
expect(headers['Content-Length']).to eq(13) expect(headers['Content-Length']).to eq(13)
expect(headers['Host']).to eq("meta.discourse.org") expect(headers['Host']).to eq("meta.discourse.org")
expect(headers['X-Discourse-Event-Id']).to eq(event.id) expect(headers['X-Discourse-Event-Id']).to eq(event.id)
expect(headers['X-Discourse-Event-Type']).to eq('ping') expect(headers['X-Discourse-Event-Type']).to eq(described_class::PING_EVENT)
expect(headers['X-Discourse-Event']).to eq('ping') expect(headers['X-Discourse-Event']).to eq(described_class::PING_EVENT)
expect(headers['X-Discourse-Event-Signature']).to eq('sha256=162f107f6b5022353274eb1a7197885cfd35744d8d08e5bcea025d309386b7d6') expect(headers['X-Discourse-Event-Signature']).to eq('sha256=162f107f6b5022353274eb1a7197885cfd35744d8d08e5bcea025d309386b7d6')
expect(event.payload).to eq(MultiJson.dump(ping: 'OK')) expect(event.payload).to eq(MultiJson.dump(ping: 'OK'))
expect(event.status).to eq(200) expect(event.status).to eq(200)

View File

@ -42,69 +42,76 @@ describe WebHook do
expect(post_hook.payload_url).to eq("https://example.com") expect(post_hook.payload_url).to eq("https://example.com")
end end
describe '#find_by_type' do describe '#active_web_hooks' do
it "returns unique hooks" do it "returns unique hooks" do
post_hook.web_hook_event_types << WebHookEventType.find_by(name: 'topic') post_hook.web_hook_event_types << WebHookEventType.find_by(name: 'topic')
post_hook.update!(wildcard_web_hook: true) post_hook.update!(wildcard_web_hook: true)
expect(WebHook.find_by_type(:post)).to eq([post_hook]) expect(WebHook.active_web_hooks(:post)).to eq([post_hook])
end end
it 'find relevant hooks' do it 'find relevant hooks' do
expect(WebHook.find_by_type(:post)).to eq([post_hook]) expect(WebHook.active_web_hooks(:post)).to eq([post_hook])
expect(WebHook.find_by_type(:topic)).to eq([topic_hook]) expect(WebHook.active_web_hooks(:topic)).to eq([topic_hook])
end end
it 'excludes inactive hooks' do it 'excludes inactive hooks' do
post_hook.update_attributes!(active: false) post_hook.update!(active: false)
expect(WebHook.find_by_type(:post)).to eq([]) expect(WebHook.active_web_hooks(:post)).to eq([])
expect(WebHook.find_by_type(:topic)).to eq([topic_hook]) expect(WebHook.active_web_hooks(:topic)).to eq([topic_hook])
end
describe 'wildcard web hooks' do
let!(:wildcard_hook) { Fabricate(:wildcard_web_hook) }
it 'should include wildcard hooks' do
expect(WebHook.active_web_hooks(:wildcard)).to eq([wildcard_hook])
expect(WebHook.active_web_hooks(:post)).to contain_exactly(
post_hook, wildcard_hook
)
expect(WebHook.active_web_hooks(:topic)).to contain_exactly(
topic_hook, wildcard_hook
)
end
end end
end end
describe '#enqueue_hooks' do describe '#enqueue_hooks' do
it 'enqueues hooks with id and name' do before do
Jobs.expects(:enqueue).with(:emit_web_hook_event, web_hook_id: post_hook.id, event_type: 'post') SiteSetting.queue_jobs = true
WebHook.enqueue_hooks(:post)
end end
it 'accepts additional parameters' do it 'accepts additional parameters' do
Jobs.expects(:enqueue).with(:emit_web_hook_event, web_hook_id: post_hook.id, post_id: 1, event_type: 'post') payload = { test: 'some payload' }.to_json
WebHook.enqueue_hooks(:post, payload: payload)
WebHook.enqueue_hooks(:post, post_id: 1) job_args = Jobs::EmitWebHookEvent.jobs.first["args"].first
end
end
context 'includes wildcard hooks' do expect(job_args["web_hook_id"]).to eq(post_hook.id)
let!(:wildcard_hook) { Fabricate(:wildcard_web_hook) } expect(job_args["event_type"]).to eq('post')
expect(job_args["payload"]).to eq(payload)
describe '#find_by_type' do
it 'can find wildcard hooks' do
expect(WebHook.find_by_type(:wildcard)).to eq([wildcard_hook])
end
it 'can include wildcard hooks' do
expect(WebHook.find_by_type(:post).sort_by(&:id)).to eq([post_hook, wildcard_hook])
expect(WebHook.find_by_type(:topic).sort_by(&:id)).to eq([topic_hook, wildcard_hook])
end
end end
describe '#enqueue_hooks' do context 'includes wildcard hooks' do
it 'enqueues hooks with ids' do let!(:wildcard_hook) { Fabricate(:wildcard_web_hook) }
Jobs.expects(:enqueue).with(:emit_web_hook_event, web_hook_id: post_hook.id, event_type: 'post')
Jobs.expects(:enqueue).with(:emit_web_hook_event, web_hook_id: wildcard_hook.id, event_type: 'post')
WebHook.enqueue_hooks(:post) describe '#enqueue_hooks' do
end it 'enqueues hooks with ids' do
WebHook.enqueue_hooks(:post)
it 'accepts additional parameters' do job_args = Jobs::EmitWebHookEvent.jobs.first["args"].first
Jobs.expects(:enqueue).with(:emit_web_hook_event, web_hook_id: post_hook.id, post_id: 1, event_type: 'post')
Jobs.expects(:enqueue).with(:emit_web_hook_event, web_hook_id: wildcard_hook.id, post_id: 1, event_type: 'post')
WebHook.enqueue_hooks(:post, post_id: 1) expect(job_args["web_hook_id"]).to eq(post_hook.id)
expect(job_args["event_type"]).to eq('post')
job_args = Jobs::EmitWebHookEvent.jobs.last["args"].first
expect(job_args["web_hook_id"]).to eq(wildcard_hook.id)
expect(job_args["event_type"]).to eq('post')
end
end end
end end
end end
@ -115,39 +122,51 @@ describe WebHook do
let(:admin) { Fabricate(:admin) } let(:admin) { Fabricate(:admin) }
let(:topic) { Fabricate(:topic, user: user) } let(:topic) { Fabricate(:topic, user: user) }
let(:post) { Fabricate(:post, topic: topic, user: user) } let(:post) { Fabricate(:post, topic: topic, user: user) }
let(:topic_web_hook) { Fabricate(:topic_web_hook) }
before do before do
SiteSetting.queue_jobs = true SiteSetting.queue_jobs = true
topic_web_hook
end
describe 'when there are no active hooks' do
it 'should not enqueue anything' do
topic_web_hook.destroy!
post = PostCreator.create(user, raw: 'post', title: 'topic', skip_validations: true)
expect(Jobs::EmitWebHookEvent.jobs.length).to eq(0)
end
end end
it 'should enqueue the right hooks for topic events' do it 'should enqueue the right hooks for topic events' do
Fabricate(:topic_web_hook)
post = PostCreator.create(user, raw: 'post', title: 'topic', skip_validations: true) post = PostCreator.create(user, raw: 'post', title: 'topic', skip_validations: true)
topic_id = post.topic_id topic_id = post.topic.id
job_args = Jobs::EmitWebHookEvent.jobs.last["args"].first job_args = Jobs::EmitWebHookEvent.jobs.last["args"].first
expect(job_args["event_name"]).to eq("topic_created") expect(job_args["event_name"]).to eq("topic_created")
expect(job_args["topic_id"]).to eq(topic_id) payload = JSON.parse(job_args["payload"])
expect(payload["id"]).to eq(topic_id)
PostDestroyer.new(user, post).destroy PostDestroyer.new(user, post).destroy
job_args = Jobs::EmitWebHookEvent.jobs.last["args"].first job_args = Jobs::EmitWebHookEvent.jobs.last["args"].first
expect(job_args["event_name"]).to eq("topic_destroyed") expect(job_args["event_name"]).to eq("topic_destroyed")
expect(job_args["topic_id"]).to eq(topic_id) payload = JSON.parse(job_args["payload"])
expect(payload["id"]).to eq(topic_id)
PostDestroyer.new(user, post).recover PostDestroyer.new(user, post).recover
job_args = Jobs::EmitWebHookEvent.jobs.last["args"].first job_args = Jobs::EmitWebHookEvent.jobs.last["args"].first
expect(job_args["event_name"]).to eq("topic_recovered") expect(job_args["event_name"]).to eq("topic_recovered")
expect(job_args["topic_id"]).to eq(topic_id) payload = JSON.parse(job_args["payload"])
expect(payload["id"]).to eq(topic_id)
%w{archived closed visible}.each do |status| %w{archived closed visible}.each do |status|
post.topic.update_status(status, true, topic.user) post.topic.update_status(status, true, topic.user)
job_args = Jobs::EmitWebHookEvent.jobs.last["args"].first job_args = Jobs::EmitWebHookEvent.jobs.last["args"].first
expect(job_args["event_name"]).to eq("topic_#{status}_status_updated") expect(job_args["event_name"]).to eq("topic_#{status}_status_updated")
expect(job_args["topic_id"]).to eq(topic_id) payload = JSON.parse(job_args["payload"])
expect(payload["id"]).to eq(topic_id)
end end
end end
@ -172,10 +191,7 @@ describe WebHook do
it 'should enqueue the right hooks for post events' do it 'should enqueue the right hooks for post events' do
Fabricate(:web_hook) Fabricate(:web_hook)
user post = PostCreator.create!(user,
topic
post = PostCreator.create(user,
raw: 'post', raw: 'post',
topic_id: topic.id, topic_id: topic.id,
reply_to_post_number: 1, reply_to_post_number: 1,
@ -183,30 +199,57 @@ describe WebHook do
) )
job_args = Jobs::EmitWebHookEvent.jobs.last["args"].first job_args = Jobs::EmitWebHookEvent.jobs.last["args"].first
Sidekiq::Worker.clear_all
expect(job_args["event_name"]).to eq("post_created") expect(job_args["event_name"]).to eq("post_created")
expect(job_args["post_id"]).to eq(post.id) payload = JSON.parse(job_args["payload"])
expect(payload["id"]).to eq(post.id)
Jobs::EmitWebHookEvent.jobs.clear
# post destroy or recover triggers a moderator post # post destroy or recover triggers a moderator post
expect { PostDestroyer.new(user, post).destroy } expect { PostDestroyer.new(user, post).destroy }
.to change { Jobs::EmitWebHookEvent.jobs.count }.by(2) .to change { Jobs::EmitWebHookEvent.jobs.count }.by(3)
job_args = Jobs::EmitWebHookEvent.jobs.first["args"].first job_args = Jobs::EmitWebHookEvent.jobs[0]["args"].first
expect(job_args["event_name"]).to eq("post_edited") expect(job_args["event_name"]).to eq("post_edited")
expect(job_args["post_id"]).to eq(post.id) payload = JSON.parse(job_args["payload"])
expect(payload["id"]).to eq(post.id)
job_args = Jobs::EmitWebHookEvent.jobs.last["args"].first job_args = Jobs::EmitWebHookEvent.jobs[1]["args"].first
expect(job_args["event_name"]).to eq("post_destroyed") expect(job_args["event_name"]).to eq("post_destroyed")
expect(job_args["post_id"]).to eq(post.id) payload = JSON.parse(job_args["payload"])
expect(payload["id"]).to eq(post.id)
PostDestroyer.new(user, post).recover job_args = Jobs::EmitWebHookEvent.jobs[2]["args"].first
job_args = Jobs::EmitWebHookEvent.jobs.last["args"].first
expect(job_args["event_name"]).to eq("topic_destroyed")
payload = JSON.parse(job_args["payload"])
expect(payload["id"]).to eq(post.topic.id)
Jobs::EmitWebHookEvent.jobs.clear
expect { PostDestroyer.new(user, post).recover }
.to change { Jobs::EmitWebHookEvent.jobs.count }.by(3)
job_args = Jobs::EmitWebHookEvent.jobs[0]["args"].first
expect(job_args["event_name"]).to eq("post_edited")
payload = JSON.parse(job_args["payload"])
expect(payload["id"]).to eq(post.id)
job_args = Jobs::EmitWebHookEvent.jobs[1]["args"].first
expect(job_args["event_name"]).to eq("post_recovered") expect(job_args["event_name"]).to eq("post_recovered")
expect(job_args["post_id"]).to eq(post.id) payload = JSON.parse(job_args["payload"])
expect(payload["id"]).to eq(post.id)
job_args = Jobs::EmitWebHookEvent.jobs[2]["args"].first
expect(job_args["event_name"]).to eq("topic_recovered")
payload = JSON.parse(job_args["payload"])
expect(payload["id"]).to eq(post.topic.id)
end end
it 'should enqueue the right hooks for user events' do it 'should enqueue the right hooks for user events' do
@ -216,37 +259,43 @@ describe WebHook do
job_args = Jobs::EmitWebHookEvent.jobs.last["args"].first job_args = Jobs::EmitWebHookEvent.jobs.last["args"].first
expect(job_args["event_name"]).to eq("user_created") expect(job_args["event_name"]).to eq("user_created")
expect(job_args["user_id"]).to eq(user.id) payload = JSON.parse(job_args["payload"])
expect(payload["id"]).to eq(user.id)
admin admin
job_args = Jobs::EmitWebHookEvent.jobs.last["args"].first job_args = Jobs::EmitWebHookEvent.jobs.last["args"].first
expect(job_args["event_name"]).to eq("user_created") expect(job_args["event_name"]).to eq("user_created")
expect(job_args["user_id"]).to eq(admin.id) payload = JSON.parse(job_args["payload"])
expect(payload["id"]).to eq(admin.id)
user.approve(admin) user.approve(admin)
job_args = Jobs::EmitWebHookEvent.jobs.last["args"].first job_args = Jobs::EmitWebHookEvent.jobs.last["args"].first
expect(job_args["event_name"]).to eq("user_approved") expect(job_args["event_name"]).to eq("user_approved")
expect(job_args["user_id"]).to eq(user.id) payload = JSON.parse(job_args["payload"])
expect(payload["id"]).to eq(user.id)
UserUpdater.new(admin, user).update(username: 'testing123') UserUpdater.new(admin, user).update(username: 'testing123')
job_args = Jobs::EmitWebHookEvent.jobs.last["args"].first job_args = Jobs::EmitWebHookEvent.jobs.last["args"].first
expect(job_args["event_name"]).to eq("user_updated") expect(job_args["event_name"]).to eq("user_updated")
expect(job_args["user_id"]).to eq(user.id) payload = JSON.parse(job_args["payload"])
expect(payload["id"]).to eq(user.id)
user.logged_out user.logged_out
job_args = Jobs::EmitWebHookEvent.jobs.last["args"].first job_args = Jobs::EmitWebHookEvent.jobs.last["args"].first
expect(job_args["event_name"]).to eq("user_logged_out") expect(job_args["event_name"]).to eq("user_logged_out")
expect(job_args["user_id"]).to eq(user.id) payload = JSON.parse(job_args["payload"])
expect(payload["id"]).to eq(user.id)
user.logged_in user.logged_in
job_args = Jobs::EmitWebHookEvent.jobs.last["args"].first job_args = Jobs::EmitWebHookEvent.jobs.last["args"].first
expect(job_args["event_name"]).to eq("user_logged_in") expect(job_args["event_name"]).to eq("user_logged_in")
expect(job_args["user_id"]).to eq(user.id) payload = JSON.parse(job_args["payload"])
expect(payload["id"]).to eq(user.id)
end end
end end
end end

View File

@ -0,0 +1,28 @@
require 'rails_helper'
RSpec.describe WebHookEnqueuer do
describe '#find_by_type' do
let(:enqueuer) { WebHookEnqueuer.new }
let!(:post_hook) { Fabricate(:web_hook, payload_url: " https://example.com ") }
let!(:topic_hook) { Fabricate(:topic_web_hook) }
it "returns unique hooks" do
post_hook.web_hook_event_types << WebHookEventType.find_by(name: 'topic')
post_hook.update!(wildcard_web_hook: true)
expect(enqueuer.find_by_type(:post)).to eq([post_hook])
end
it 'find relevant hooks' do
expect(enqueuer.find_by_type(:post)).to eq([post_hook])
expect(enqueuer.find_by_type(:topic)).to eq([topic_hook])
end
it 'excludes inactive hooks' do
post_hook.update!(active: false)
expect(enqueuer.find_by_type(:post)).to eq([])
expect(enqueuer.find_by_type(:topic)).to eq([topic_hook])
end
end
end