Skip to content

Commit

Permalink
Cover sending events to rabbitmq
Browse files Browse the repository at this point in the history
I was mildly annoyed that I couldn't cover the new events to
be sent to rabbitmq, so I did a little research on how to test
bunny. It's not so easy, but good enough to mock
  • Loading branch information
coolo committed Nov 14, 2018
1 parent cc24b27 commit 2662760
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 9 deletions.
2 changes: 2 additions & 0 deletions src/api/Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ group :test do
gem 'coveralls'
gem 'minitest-ci'
gem 'rspec_junit_formatter'
# to test rabbitmq support
gem 'bunny-mock'
end

group :development, :test do
Expand Down
3 changes: 3 additions & 0 deletions src/api/Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ GEM
uniform_notifier (~> 1.11)
bunny (2.12.0)
amq-protocol (~> 2.3, >= 2.3.0)
bunny-mock (1.7.0)
bunny (>= 1.7)
byebug (10.0.2)
capybara (3.10.1)
addressable
Expand Down Expand Up @@ -457,6 +459,7 @@ DEPENDENCIES
builder
bullet
bunny
bunny-mock
capybara
capybara_minitest_spec
chunky_png
Expand Down
29 changes: 20 additions & 9 deletions src/api/lib/rabbitmq_bus.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
class RabbitmqBus
cattr_accessor :connection, :exchange

def self.send_to_bus(channel, data)
publish("#{Configuration.amqp_namespace}.#{channel}", data)
rescue => e
Expand All @@ -9,21 +11,30 @@ def self.send_to_bus(channel, data)
def self.publish(event_routing_key, event_payload)
return unless CONFIG['amqp_options']
start_connection

$rabbitmq_exchange.publish(event_payload, routing_key: event_routing_key)
wrapped_exchange.publish(event_payload, routing_key: event_routing_key)
end

# Start one connection, channel and exchange per rails process
# and reuse them
def self.start_connection
$rabbitmq_conn ||= Bunny.new(CONFIG['amqp_options'].try(:symbolize_keys))
$rabbitmq_conn.start
$rabbitmq_channel ||= $rabbitmq_conn.create_channel
$rabbitmq_exchange = if CONFIG['amqp_exchange_name']
$rabbitmq_channel.exchange(CONFIG['amqp_exchange_name'], CONFIG['amqp_exchange_options'].try(:symbolize_keys) || {})
def self.wrapped_exchange
return exchange if exchange
connection.start
rabbitmq_channel = connection.create_channel
if CONFIG['amqp_exchange_name']
self.exchange = rabbitmq_channel.exchange(CONFIG['amqp_exchange_name'], CONFIG['amqp_exchange_options'].try(:symbolize_keys) || {})
else
$rabbitmq_channel.default_exchange
# can't cover due to https://github.com/arempe93/bunny-mock/pull/25
#:nocov:
self.exchange = rabbitmq_channel.default_exchange
#:nocov:
end
end

# this function is skipped in tests by putting a BunnyMock in self.connection
def self.start_connection
#:nocov:
self.connection ||= Bunny.new(CONFIG['amqp_options'].try(:symbolize_keys))
#:nocov:
end
private_class_method :start_connection
end
19 changes: 19 additions & 0 deletions src/api/spec/lib/rabbitmq_bus_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
require 'rails_helper'

RSpec.describe RabbitmqBus, rabbitmq: '#' do
it 'publishes messages' do
RabbitmqBus.send_to_bus('metrics', 'hallo')
expect_message('opensuse.obs.metrics', 'hallo')
end

context 'with exceptions' do
before do
allow_any_instance_of(BunnyMock::Queue).to receive(:publish).and_raise(Net::ReadTimeout)
end

it 'disconnects on errors' do
RabbitmqBus.send_to_bus('metrics', 'hallo')
expect_no_message
end
end
end
49 changes: 49 additions & 0 deletions src/api/spec/models/update_notification_events_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
require 'rails_helper'

RSpec.describe UpdateNotificationEvents, type: :model do
let!(:admin) { create(:admin_user, login: 'Admin') }
let(:notifications) do
<<-RESPONSE
<notifications next="3">
<notification type="SRCSRV_CREATE_PROJECT" time="1539445101">
<data key="project">project_1</data>
<data key="sender">_nobody_</data>
</notification>
<notification type="SRCSRV_CREATE_PACKAGE" time="1539445101">
<data key="package">multibuild</data>
<data key="project">project_1</data>
<data key="sender">_nobody_</data>
</notification>
</notifications>
RESPONSE
end

before do
url = CONFIG['source_url'] + '/lastnotifications?block=1&start=1'
stub_request(:get, url).and_return(body: notifications)
end

it 'fetches events' do
UpdateNotificationEvents.new.perform
expect(BackendInfo.lastnotification_nr).to eq(3)
end

context 'with configured amqp', rabbitmq: '#' do
it 'sends events' do
# not interested in setup messages
empty_message_queue

UpdateNotificationEvents.new.perform

# SRCSRV_CREATE_PROJECT
expect_message('opensuse.obs.project.create', '{"project":"project_1","sender":"_nobody_"}')
expect_message('opensuse.obs.metrics', 'project.create,home=false value=1')

# SRCSRV_CREATE_PACKAGE
expect_message('opensuse.obs.package.create', '{"project":"project_1","package":"multibuild","sender":"_nobody_"}')
expect_message('opensuse.obs.metrics', 'package.create,home=false value=1')

expect_no_message
end
end
end
3 changes: 3 additions & 0 deletions src/api/spec/rails_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,6 @@

# silence migration tests
require 'support/migration'

# support rabbitmq
require 'support/rabbitmq'
34 changes: 34 additions & 0 deletions src/api/spec/support/rabbitmq.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
module RabbitMQHelpers
cattr_accessor :test_queue

def empty_message_queue
test_queue.purge
end

def expect_no_message
expect(test_queue.message_count).to eq(0)
end

def expect_message(routing_key, message)
expect(test_queue.all.shift).to include(message: message, options: { routing_key: routing_key, exchange: 'pubsub' })
end
end

RSpec.configure do |config|
config.before do |example|
if example.metadata[:rabbitmq]
config.include RabbitMQHelpers
# define config - not taken into account though due to mocking
stub_const('CONFIG', CONFIG.merge('amqp_options' => { dummy: 1 }, 'amqp_exchange_name' => 'pubsub', 'amqp_exchange_options' => { type: :topic }))

connection = BunnyMock.new
RabbitmqBus.connection = connection
# setup exchange
RabbitmqBus.exchange = nil
RabbitmqBus.wrapped_exchange

RabbitMQHelpers.test_queue = connection.channel.queue('test')
RabbitMQHelpers.test_queue.bind('pubsub', routing_key: example.metadata[:rabbitmq])
end
end
end

0 comments on commit 2662760

Please sign in to comment.