From 160c77b2ca3918aac8ac72b8a57b9f99246f4193 Mon Sep 17 00:00:00 2001 From: Mike Blumtritt Date: Wed, 25 Oct 2023 16:02:02 +0200 Subject: [PATCH 1/8] Extend tests for Ears --- lib/ears.rb | 12 +- spec/ears_spec.rb | 261 +++++++++++++++++++------------------------- spec/spec_helper.rb | 15 +-- 3 files changed, 122 insertions(+), 166 deletions(-) diff --git a/lib/ears.rb b/lib/ears.rb index 54e6d49..13bb12f 100644 --- a/lib/ears.rb +++ b/lib/ears.rb @@ -8,11 +8,9 @@ module Ears class << self # The global configuration for Ears. - # + # @attribute [r] configuration # @return [Ears::Configuration] - def configuration - @configuration ||= Ears::Configuration.new - end + attr_reader :configuration # Yields the global configuration instance so you can modify it. # @yieldparam configuration [Ears::Configuration] The global configuration instance. @@ -75,8 +73,8 @@ def error!(error) # Used internally for testing. def reset! @connection = nil - @configuration = nil Thread.current[:ears_channel] = nil + @configuration = Ears::Configuration.new end private @@ -100,8 +98,10 @@ def connection_config recover_from_connection_close: configuration.recover_from_connection_close, recovery_attempts: configuration.recovery_attempts, - recovery_attempts_exhausted: configuration.recovery_attempts_exhausted, + recovery_attempts_exhausted: configuration.recovery_attempts_exhausted }.compact end end + + reset! end diff --git a/spec/ears_spec.rb b/spec/ears_spec.rb index bd21f9f..ffd75fe 100644 --- a/spec/ears_spec.rb +++ b/spec/ears_spec.rb @@ -1,20 +1,22 @@ -require 'ears' +# frozen_string_literal: true RSpec.describe Ears do - let(:bunny) { instance_double(Bunny::Session) } - let(:channel) { instance_double(Bunny::Channel) } - - before do - Ears.reset! - allow(Bunny).to receive(:new).and_return(bunny) - allow(bunny).to receive(:start) - allow(bunny).to receive(:create_channel).and_return(channel) - allow(channel).to receive(:prefetch).with(1) - allow(channel).to receive(:on_uncaught_exception) - end + # let(:bunny) { instance_double(Bunny::Session) } + # let(:channel) { instance_double(Bunny::Channel) } + + # before do + # Ears.reset! + # allow(Bunny).to receive(:new).and_return(bunny) + # allow(bunny).to receive(:start) + # allow(bunny).to receive(:create_channel).and_return(channel) + # allow(channel).to receive(:prefetch).with(1) + # allow(channel).to receive(:on_uncaught_exception) + # end it 'has a version number' do - expect(Ears::VERSION).not_to be_nil + expect(Ears::VERSION).to match( + /\A[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+\z/ + ) end it 'has a configuration' do @@ -22,202 +24,163 @@ end describe '.configure' do + before { Ears.reset! } + it 'allows setting the configuration values' do Ears.configure do |config| - config.rabbitmq_url = 'test' - config.connection_name = 'conn' + config.rabbitmq_url = 'amqp://guest:guest@test.local:5672' + config.connection_name = 'conn_name' + config.recover_from_connection_close = true + config.recovery_attempts = 666 end - expect(Ears.configuration.rabbitmq_url).to eq('test') + expect(Ears.configuration).to have_attributes( + rabbitmq_url: 'amqp://guest:guest@test.local:5672', + connection_name: 'conn_name', + recover_from_connection_close: true, + recovery_attempts: 666 + ) end - it 'throws an error if connection name was not set' do - Ears.reset! + it 'yields the Ears configuration' do + Ears.configure do |config| + config.connection_name = 'conn_name' + expect(config).to be Ears.configuration + end + end - expect { - Ears.configure { |config| config.rabbitmq_url = 'test' } - }.to raise_error(Ears::Configuration::ConnectionNameMissing) + it 'throws an error if connection name was not set' do + expect do + Ears.configure(&:to_s) + end.to raise_error Ears::Configuration::ConnectionNameMissing end end describe '.connection' do - let(:rabbitmq_url) { 'amqp://lol:lol@kek.com:15672' } - let(:connection_name) { 'my connection' } + let(:bunny_session) { instance_double(Bunny::Session, start: nil) } - context 'when only mandatory options are set' do + context 'with mandatory configuration' do before do - Ears.configure do |config| - config.rabbitmq_url = rabbitmq_url - config.connection_name = connection_name - end - end - - it 'connects with default config parameters when it is accessed' do + allow(Bunny).to receive(:new).and_return(bunny_session) + Ears.reset! + Ears.configure { |config| config.connection_name = 'here_we_go' } Ears.connection + end + it 'connects with config parameters' do expect(Bunny).to have_received(:new).with( - rabbitmq_url, - connection_name: connection_name, + 'amqp://guest:guest@localhost:5672', + connection_name: 'here_we_go', recovery_attempts: 10, - recovery_attempts_exhausted: anything, - ) do |_args, kwargs| - proc = kwargs[:recovery_attempts_exhausted] - expect { proc.call }.to raise_error( - Ears::MaxRecoveryAttemptsExhaustedError, - ) - end - expect(bunny).to have_received(:start) + recovery_attempts_exhausted: anything + ) end - end - context 'with more options' do - let(:recover_from_connection_close) { false } - let(:recovery_attempts) { nil } + it 'starts the connection' do + expect(bunny_session).to have_received(:start) + end + end + context 'with custom configration' do before do + allow(Bunny).to receive(:new).and_return(bunny_session) + Ears.reset! Ears.configure do |config| - config.rabbitmq_url = rabbitmq_url - config.connection_name = connection_name - config.recover_from_connection_close = recover_from_connection_close - config.recovery_attempts = recovery_attempts + config.rabbitmq_url = 'amqp://lol:lol@kek.com:15672' + config.connection_name = 'here_we_go' + config.recover_from_connection_close = false + config.recovery_attempts = 9 end - end - - it 'connects with config parameters when it is accessed' do Ears.connection + end + it 'connects with config parameters' do expect(Bunny).to have_received(:new).with( - rabbitmq_url, - connection_name: connection_name, - recover_from_connection_close: recover_from_connection_close, + 'amqp://lol:lol@kek.com:15672', + connection_name: 'here_we_go', + recover_from_connection_close: false, + recovery_attempts: 9, + recovery_attempts_exhausted: anything ) - expect(bunny).to have_received(:start) + end + + it 'starts the connection' do + expect(bunny_session).to have_received(:start) end end end describe '.channel' do - it 'creates a channel when it is accessed' do - expect(bunny).to receive(:create_channel).with(nil, 1, true).and_return( - channel, - ) - expect(channel).to receive(:prefetch).with(1) - expect(channel).to receive(:on_uncaught_exception) - - Ears.channel + let(:bunny_session) do + instance_double(Bunny::Session, start: nil, create_channel: bunny_channel) end - it 'stores the channel on the current thread' do - expect(Ears.channel).to eq(Thread.current[:ears_channel]) + let(:bunny_channel) do + instance_double(Bunny::Channel, prefetch: nil, on_uncaught_exception: nil) end - end - - describe '.setup' do - let(:exchange) { instance_double(Bunny::Exchange) } - let(:queue) { instance_double(Bunny::Queue, name: 'queue', options: {}) } - let(:consumer_wrapper) { instance_double(Ears::ConsumerWrapper) } - let(:delivery_info) { instance_double(Bunny::DeliveryInfo) } - let(:metadata) { instance_double(Bunny::MessageProperties) } - let(:payload) { 'my payload' } before do - allow(Bunny::Exchange).to receive(:new).and_return(exchange) - allow(Bunny::Queue).to receive(:new).and_return(queue) - allow(queue).to receive(:bind) - allow(queue).to receive(:subscribe_with) - allow(queue).to receive(:channel).and_return(channel) - allow(Ears::ConsumerWrapper).to receive(:new).and_return(consumer_wrapper) - allow(consumer_wrapper).to receive(:on_delivery).and_yield( - delivery_info, - metadata, - payload, - ) - allow(Thread).to receive(:new).and_yield - - stub_const('MyConsumer', Class.new(Ears::Consumer)) + allow(Bunny).to receive(:new).and_return(bunny_session) + Ears.channel end - it 'creates a given exchange' do - expect(Bunny::Exchange).to receive(:new).with( - channel, - :topic, - 'my-exchange', - {}, - ) - - Ears.setup { exchange('my-exchange', :topic) } + it 'creates a channel when it is accessed' do + expect(bunny_session).to have_received(:create_channel).with(nil, 1, true) end - it 'creates a queue' do - expect(Bunny::Queue).to receive(:new).with(channel, 'my-queue', {}) - - Ears.setup do - exchange('my-exchange', :topic) - queue('my-queue') - end + it 'configures the channel prefetch' do + expect(bunny_session).to have_received(:prefetch).with(1) end - it 'binds a queue to an exchange' do - expect(queue).to receive(:bind).with(exchange, routing_key: 'test') + it 'configures the channel exception handler' do + expect(bunny_channel).to have_received(:on_uncaught_exception) + end - Ears.setup do - exchange = exchange('my-exchange', :topic) - queue = queue('my-queue') - queue.bind(exchange, routing_key: 'test') - end + it 'stores the channel on the current thread' do + expect(Ears.channel).to eq(Thread.current[:ears_channel]) end + end - it 'starts a consumer subscribed to a queue' do - expect(consumer_wrapper).to receive(:on_delivery).and_yield( - delivery_info, - metadata, - payload, - ).ordered - expect(consumer_wrapper).to receive(:process_delivery).with( - delivery_info, - metadata, - payload, - ).ordered - expect(queue).to receive(:subscribe_with).with(consumer_wrapper).ordered - - Ears.setup do - exchange = exchange('my-exchange', :topic) - queue = queue('my-queue') - queue.bind(exchange, routing_key: 'test') - consumer(queue, MyConsumer) - end + describe '.setup' do + it 'creates a setup helper and executed the given block on this instace' do + instance = :none + Ears.setup { instance = self } + expect(instance).to be_a Ears::Setup end end describe '.stop!' do - before { allow(bunny).to receive(:close) } - - it 'stops the connection' do - Ears.stop! - - expect(bunny).to have_received(:close) + let(:bunny_session) do + instance_double( + Bunny::Session, + start: nil, + create_channel: bunny_channel, + close: nil + ) end - it 'resets the connection' do - Ears.connection + let(:bunny_channel) do + instance_double(Bunny::Channel, prefetch: nil, on_uncaught_exception: nil) + end + before do + allow(Bunny).to receive(:new).and_return(bunny_session) + Ears.channel Ears.stop! + end - Ears.connection - Ears.connection + it 'stops the connection' do + expect(bunny_session).to have_received(:close) + end + it 'forces to create a new connection afterwards' do + Ears.connection expect(Bunny).to have_received(:new).twice end - it 'resets the channel' do - Ears.channel - - Ears.stop! - + it 'forces to create a new channel afterwards' do Ears.channel - Ears.channel - - expect(bunny).to have_received(:create_channel).twice + expect(bunny_session).to have_received(:create_channel).twice end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 41a243b..99e0ae9 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,13 +1,6 @@ -require 'bundler/setup' +# frozen_string_literal: true -RSpec.configure do |config| - # Enable flags like --only-failures and --next-failure - config.example_status_persistence_file_path = '.rspec_status' +require 'rspec/core' +require 'ears' - # Disable RSpec exposing methods globally on `Module` and `main` - config.disable_monkey_patching! - - config.expect_with :rspec do |c| - c.syntax = :expect - end -end +RSpec.configure(&:disable_monkey_patching!) From 80e30d0bc24cc83764dcf472689cc5ab47f3252f Mon Sep 17 00:00:00 2001 From: Mike Blumtritt Date: Wed, 25 Oct 2023 16:13:08 +0200 Subject: [PATCH 2/8] Tidy.up --- Rakefile | 3 +-- lib/ears.rb | 2 +- spec/ears_spec.rb | 28 +++++++--------------------- 3 files changed, 9 insertions(+), 24 deletions(-) diff --git a/Rakefile b/Rakefile index 1e95399..baf74c4 100644 --- a/Rakefile +++ b/Rakefile @@ -6,8 +6,7 @@ require 'rspec/core/rake_task' require 'yard' CLEAN << '.yardoc' -CLOBBER << 'doc' -CLOBBER << 'coverage' +CLOBBER << 'doc' << 'coverage' RSpec::Core::RakeTask.new(:spec) YARD::Rake::YardocTask.new { |t| t.stats_options = %w[--list-undoc] } diff --git a/lib/ears.rb b/lib/ears.rb index 13bb12f..9f1caed 100644 --- a/lib/ears.rb +++ b/lib/ears.rb @@ -98,7 +98,7 @@ def connection_config recover_from_connection_close: configuration.recover_from_connection_close, recovery_attempts: configuration.recovery_attempts, - recovery_attempts_exhausted: configuration.recovery_attempts_exhausted + recovery_attempts_exhausted: configuration.recovery_attempts_exhausted, }.compact end end diff --git a/spec/ears_spec.rb b/spec/ears_spec.rb index ffd75fe..b0e4131 100644 --- a/spec/ears_spec.rb +++ b/spec/ears_spec.rb @@ -1,21 +1,11 @@ # frozen_string_literal: true RSpec.describe Ears do - # let(:bunny) { instance_double(Bunny::Session) } - # let(:channel) { instance_double(Bunny::Channel) } - - # before do - # Ears.reset! - # allow(Bunny).to receive(:new).and_return(bunny) - # allow(bunny).to receive(:start) - # allow(bunny).to receive(:create_channel).and_return(channel) - # allow(channel).to receive(:prefetch).with(1) - # allow(channel).to receive(:on_uncaught_exception) - # end + before { Ears.reset! } it 'has a version number' do expect(Ears::VERSION).to match( - /\A[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+\z/ + /\A[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+\z/, ) end @@ -24,8 +14,6 @@ end describe '.configure' do - before { Ears.reset! } - it 'allows setting the configuration values' do Ears.configure do |config| config.rabbitmq_url = 'amqp://guest:guest@test.local:5672' @@ -38,7 +26,7 @@ rabbitmq_url: 'amqp://guest:guest@test.local:5672', connection_name: 'conn_name', recover_from_connection_close: true, - recovery_attempts: 666 + recovery_attempts: 666, ) end @@ -62,7 +50,6 @@ context 'with mandatory configuration' do before do allow(Bunny).to receive(:new).and_return(bunny_session) - Ears.reset! Ears.configure { |config| config.connection_name = 'here_we_go' } Ears.connection end @@ -72,7 +59,7 @@ 'amqp://guest:guest@localhost:5672', connection_name: 'here_we_go', recovery_attempts: 10, - recovery_attempts_exhausted: anything + recovery_attempts_exhausted: anything, ) end @@ -84,7 +71,6 @@ context 'with custom configration' do before do allow(Bunny).to receive(:new).and_return(bunny_session) - Ears.reset! Ears.configure do |config| config.rabbitmq_url = 'amqp://lol:lol@kek.com:15672' config.connection_name = 'here_we_go' @@ -100,7 +86,7 @@ connection_name: 'here_we_go', recover_from_connection_close: false, recovery_attempts: 9, - recovery_attempts_exhausted: anything + recovery_attempts_exhausted: anything, ) end @@ -129,7 +115,7 @@ end it 'configures the channel prefetch' do - expect(bunny_session).to have_received(:prefetch).with(1) + expect(bunny_channel).to have_received(:prefetch).with(1) end it 'configures the channel exception handler' do @@ -155,7 +141,7 @@ Bunny::Session, start: nil, create_channel: bunny_channel, - close: nil + close: nil, ) end From 21739993b5723fe3b66f5dd690992419b1e73a24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Lu=CC=88dke?= Date: Thu, 26 Oct 2023 09:55:12 +0200 Subject: [PATCH 3/8] Improve local tests --- Rakefile | 12 +++++++++++- spec/spec_helper.rb | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Rakefile b/Rakefile index baf74c4..db90048 100644 --- a/Rakefile +++ b/Rakefile @@ -2,6 +2,8 @@ require 'rake/clean' require 'bundler/gem_tasks' +require 'rubocop' +require 'rubocop/rake_task' require 'rspec/core/rake_task' require 'yard' @@ -11,4 +13,12 @@ CLOBBER << 'doc' << 'coverage' RSpec::Core::RakeTask.new(:spec) YARD::Rake::YardocTask.new { |t| t.stats_options = %w[--list-undoc] } -task default: :spec +RuboCop::RakeTask.new(:rubocop) do |task| + task.formatters = ['simple'] + task.fail_on_error = true +end + +desc 'Run Prettier' +task(:prettier) { system('npm run lint') } + +task default: %i[spec rubocop prettier] diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 3993206..d93e7ac 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -13,5 +13,5 @@ if RSpec.configuration.files_to_run.length > 1 # Let's increase this later on - SimpleCov.minimum_coverage line: 97.5, branch: 65.7 + SimpleCov.minimum_coverage line: 99.4, branch: 100 end From bdf1215ae91fdd1fa9d37f42223ae3421744e9a6 Mon Sep 17 00:00:00 2001 From: Mike Blumtritt Date: Thu, 26 Oct 2023 13:23:32 +0200 Subject: [PATCH 4/8] Refactor Ears::Setup tests --- lib/ears/setup.rb | 12 +- spec/ears/setup_spec.rb | 404 ++++++++++++++++------------------------ 2 files changed, 169 insertions(+), 247 deletions(-) diff --git a/lib/ears/setup.rb b/lib/ears/setup.rb index 6484d3c..48e254f 100644 --- a/lib/ears/setup.rb +++ b/lib/ears/setup.rb @@ -36,7 +36,7 @@ def queue(name, opts = {}) Bunny::Queue.new( Ears.channel, name, - queue_options(bunny_opts, retry_args), + queue_options(bunny_opts, retry_args) ) end @@ -72,7 +72,7 @@ def retry_arguments(name, opts) { 'x-dead-letter-exchange' => '', - 'x-dead-letter-routing-key' => "#{name}.retry", + 'x-dead-letter-routing-key' => "#{name}.retry" } end @@ -82,7 +82,7 @@ def create_consumer(queue, consumer_class, args, number) queue.channel, queue, "#{consumer_class.name}-#{number}", - args, + args ) end @@ -104,7 +104,7 @@ def create_retry_queue(name, delay, opts) Bunny::Queue.new( Ears.channel, "#{name}.retry", - opts.merge(retry_queue_opts(name, delay)), + opts.merge(retry_queue_opts(name, delay)) ) end @@ -113,8 +113,8 @@ def retry_queue_opts(name, delay) arguments: { 'x-message-ttl' => delay, 'x-dead-letter-exchange' => '', - 'x-dead-letter-routing-key' => name, - }, + 'x-dead-letter-routing-key' => name + } } end diff --git a/spec/ears/setup_spec.rb b/spec/ears/setup_spec.rb index 747a579..4569ac2 100644 --- a/spec/ears/setup_spec.rb +++ b/spec/ears/setup_spec.rb @@ -1,289 +1,211 @@ -require 'ears' -require 'ears/setup' +# frozen_string_literal: true RSpec.describe Ears::Setup do - let(:connection) { instance_double(Bunny::Session) } - let(:channel) { instance_double(Bunny::Channel, number: 1) } - let(:exchange) { instance_double(Bunny::Exchange) } - let(:queue) { instance_double(Bunny::Queue, name: 'queue', options: {}) } + subject(:setup) { Ears::Setup.new } - before do - allow(connection).to receive(:create_channel).and_return(channel) - allow(Ears).to receive_messages(connection: connection, channel: channel) - allow(channel).to receive(:prefetch) - allow(channel).to receive(:on_uncaught_exception) - allow(Bunny::Queue).to receive(:new).and_return(queue) - allow(queue).to receive(:channel).and_return(channel) - end - - describe '#exchange' do - it 'creates a new bunny exchange with the given options' do - expect(Bunny::Exchange).to receive(:new).with( - channel, - :topic, - 'name', - {}, - ).and_return(exchange) + let(:ears_channel) { instance_double(Bunny::Channel) } - expect(Ears::Setup.new.exchange('name', :topic)).to eq(exchange) - end + before { allow(Ears).to receive(:channel).and_return(ears_channel) } - it 'passes the given options to the exchange' do + describe '#exchange' do + it 'creates a new Bunny exchange with the given options' do expect(Bunny::Exchange).to receive(:new).with( - channel, - :topic, - 'name', - { test: 1 }, - ).and_return(exchange) - - expect(Ears::Setup.new.exchange('name', :topic, { test: 1 })).to eq( - exchange, + ears_channel, + :type, + :name, + :some_options ) + + setup.exchange(:name, :type, :some_options) end end describe '#queue' do - it 'creates a new bunny queue with the given options' do + it 'creates the Bunny queue' do expect(Bunny::Queue).to receive(:new).with( - channel, - 'name', - {}, - ).and_return(queue) + ears_channel, + 'aName', + { + custom_argument: :forwared, + arguments: { + 'x-custom-argument' => 'forwarded' + } + } + ) - expect(Ears::Setup.new.queue('name')).to eq(queue) + setup.queue( + 'aName', + custom_argument: :forwared, + arguments: { 'x-custom-argument' => 'forwarded' }.freeze + ) end - it 'passes the given options to the queue' do + it 'creates optionally a related retry queue' do expect(Bunny::Queue).to receive(:new).with( - channel, - 'name', - { test: 1 }, - ).and_return(queue) - - expect(Ears::Setup.new.queue('name', { test: 1 })).to eq(queue) - end - - context 'with retry queue' do - it 'does not pass on options that are processed by ears' do - expect(Bunny::Queue).to receive(:new).with( - channel, - 'name', - { - test: 1, - arguments: { - 'x-dead-letter-exchange' => '', - 'x-dead-letter-routing-key' => 'name.retry', - }, - }, - ).and_return(queue) - - expect( - Ears::Setup.new.queue( - 'name', - { retry_queue: true, retry_delay: 1000, test: 1 }, - ), - ).to eq(queue) - end - - it 'creates a retry queue with a derived name' do - expect(Bunny::Queue).to receive(:new).with( - channel, - 'name.retry', - { - arguments: { - 'x-message-ttl' => 5000, - 'x-dead-letter-exchange' => '', - 'x-dead-letter-routing-key' => 'name', - }, - }, - ) - - expect(Ears::Setup.new.queue('name', retry_queue: true)).to eq(queue) - end - - it 'adds the retry queue as a deadletter to the original queue' do - expect(Bunny::Queue).to receive(:new).with( - channel, - 'name', - { - arguments: { - 'x-dead-letter-exchange' => '', - 'x-dead-letter-routing-key' => 'name.retry', - }, - }, - ) - - expect(Ears::Setup.new.queue('name', retry_queue: true)).to eq(queue) - end - - it 'uses the given retry delay for the retry queue' do - expect(Bunny::Queue).to receive(:new).with( - channel, - 'name.retry', - { - arguments: { - 'x-message-ttl' => 1000, - 'x-dead-letter-exchange' => '', - 'x-dead-letter-routing-key' => 'name', - }, - }, - ) - - expect( - Ears::Setup.new.queue('name', retry_queue: true, retry_delay: 1000), - ).to eq(queue) - end + ears_channel, + 'aName', + { + custom_argument: :forwared, + arguments: { + 'x-dead-letter-exchange' => '', + 'x-dead-letter-routing-key' => 'aName.retry', + 'x-custom-argument' => 'forwarded' + } + } + ) - it 'merges retry options into the arguments' do - expect(Bunny::Queue).to receive(:new).with( - channel, - 'name', - { - arguments: { - 'x-max-length-bytes' => 1_048_576, - 'x-overflow' => 'reject-publish', - 'x-dead-letter-exchange' => '', - 'x-dead-letter-routing-key' => 'name.retry', - }, - }, - ).and_return(queue) + expect(Bunny::Queue).to receive(:new).with( + ears_channel, + 'aName.retry', + { + custom_argument: :forwared, + arguments: { + 'x-dead-letter-exchange' => '', + 'x-dead-letter-routing-key' => 'aName', + 'x-message-ttl' => 311 + } + } + ) - expect( - Ears::Setup.new.queue( - 'name', - { - retry_queue: true, - retry_delay: 1000, - arguments: { - 'x-max-length-bytes' => 1_048_576, - 'x-overflow' => 'reject-publish', - }, - }, - ), - ).to eq(queue) - end + setup.queue( + 'aName', + retry_queue: true, + retry_delay: 311, + custom_argument: :forwared, + arguments: { 'x-custom-argument' => 'forwarded' }.freeze + ) end - context 'with error queue' do - it 'creates an error queue with derived name if option is set' do - expect(Bunny::Queue).to receive(:new).with(channel, 'name.error', {}) + it 'creates optionally a related error queue' do + expect(Bunny::Queue).to receive(:new).with( + ears_channel, + 'aName', + { + custom_argument: :forwared, + arguments: { + 'x-custom-argument' => 'forwarded' + } + } + ) - expect(Ears::Setup.new.queue('name', error_queue: true)).to eq(queue) - end + expect(Bunny::Queue).to receive(:new).with( + ears_channel, + 'aName.error', + { + custom_argument: :forwared, + arguments: { + 'x-custom-argument' => 'forwarded' + } + } + ) + + setup.queue( + 'aName', + error_queue: true, + custom_argument: :forwared, + arguments: { 'x-custom-argument' => 'forwarded' }.freeze + ) end end describe '#consumer' do - let(:consumer_class) { Class.new(Ears::Consumer) } - let(:consumer_instance) { instance_double(consumer_class) } - let(:consumer_wrapper) { instance_double(Ears::ConsumerWrapper) } - let(:delivery_info) { instance_double(Bunny::DeliveryInfo) } - let(:metadata) { instance_double(Bunny::MessageProperties) } - let(:payload) { 'my payload' } - - before do - allow(consumer_class).to receive(:new).and_return(consumer_instance) - allow(Ears::ConsumerWrapper).to receive(:new).and_return(consumer_wrapper) - allow(consumer_wrapper).to receive(:on_delivery) - allow(queue).to receive(:subscribe_with) - stub_const('MyConsumer', consumer_class) + let(:ears_connection) do + instance_double(Bunny::Session, create_channel: consumer_channel) end - - it 'instantiates the given class and registers it as a consumer' do - expect(Ears::ConsumerWrapper).to receive(:new).with( - consumer_instance, - channel, - queue, - 'MyConsumer-1', - {}, - ).and_return(consumer_wrapper) - expect(consumer_wrapper).to receive(:on_delivery).and_yield( - delivery_info, - metadata, - payload, - ) - expect(consumer_wrapper).to receive(:process_delivery).with( - delivery_info, - metadata, - payload, + let(:arg_queue) do + instance_double( + Bunny::Queue, + name: 'aQueue', + options: { + queue: :options + } ) - expect(queue).to receive(:subscribe_with).with(consumer_wrapper) - - Ears::Setup.new.consumer(queue, MyConsumer) end - - it 'passes the consumer arguments' do - expect(Ears::ConsumerWrapper).to receive(:new).with( - consumer_instance, - channel, - queue, - 'MyConsumer-1', - { a: 1 }, - ).and_return(consumer_wrapper) - expect(consumer_wrapper).to receive(:on_delivery).and_yield( - delivery_info, - metadata, - payload, + let(:consumer_class) do + instance_double(Class, name: 'SampleConsumer', new: consumer_instance) + end + let(:consumer_instance) { instance_double(Object) } + let(:consumer_queue) do + instance_double( + Bunny::Queue, + name: 'ConsumerQueue', + channel: consumer_channel, + subscribe_with: nil ) - expect(consumer_wrapper).to receive(:process_delivery).with( - delivery_info, - metadata, - payload, + end + let(:consumer_channel) do + instance_double( + Bunny::Channel, + prefetch: nil, + on_uncaught_exception: nil, + number: 11 ) - expect(queue).to receive(:subscribe_with).with(consumer_wrapper) + end - Ears::Setup.new.consumer(queue, MyConsumer, 1, { a: 1 }) + before do + allow(Ears).to receive(:connection).and_return(ears_connection) + allow(Bunny::Queue).to receive(:new).and_return(consumer_queue) end - it 'creates a dedicated channel and queue for each consumer' do - expect(connection).to receive(:create_channel) + it 'creates the specified number of channels' do + expect(ears_connection).to receive(:create_channel) .with(nil, 1, true) - .and_return(channel) - .exactly(3) - .times - expect(channel).to receive(:prefetch).with(1).exactly(3).times - expect(channel).to receive(:on_uncaught_exception).exactly(3).times - expect(Bunny::Queue).to receive(:new) - .with(channel, 'queue', {}) - .and_return(queue) - .exactly(3) - .times - expect(queue).to receive(:subscribe_with) - .with(consumer_wrapper) .exactly(3) .times - Ears::Setup.new.consumer(queue, MyConsumer, 3) + setup.consumer(arg_queue, consumer_class, 3) end - it 'passes the prefetch argument to the channel' do - expect(channel).to receive(:prefetch).with(5) + it 'setups each channel' do + expect(consumer_channel).to receive(:prefetch).with(15).once + expect(consumer_channel).to receive(:on_uncaught_exception).once - Ears::Setup.new.consumer( - queue, - MyConsumer, + setup.consumer( + arg_queue, + consumer_class, 1, - { prefetch: 5, bla: 'test' }, + { prefetch: 15, custom: :options } ) end - it 'numbers the consumers' do - expect(Ears::ConsumerWrapper).to receive(:new).with( - consumer_instance, - channel, - queue, - 'MyConsumer-1', - {}, - ).and_return(consumer_wrapper) - expect(Ears::ConsumerWrapper).to receive(:new).with( - consumer_instance, - channel, - queue, - 'MyConsumer-2', - {}, - ).and_return(consumer_wrapper) + it 'creates the specified number of queues' do + expect(Bunny::Queue).to receive(:new) + .exactly(4) + .times + .with(consumer_channel, arg_queue.name, arg_queue.options) + + setup.consumer(arg_queue, consumer_class, 4) + end + + it 'setups each created queue' do + expect(consumer_queue).to receive(:subscribe_with).once + + setup.consumer(arg_queue, consumer_class, 1) + end + + it 'creates the specified number of consumers' do + expect(Ears::ConsumerWrapper).to receive(:new) + .and_call_original + .exactly(5) + .times + expect(consumer_class).to receive(:new).exactly(5).times + + setup.consumer(arg_queue, consumer_class, 5) + end + + it 'setups the consumer wrappers' do + expect(Ears::ConsumerWrapper).to receive(:new) + .with( + consumer_instance, + consumer_channel, + consumer_queue, + "#{consumer_class.name}-1", + { custom: :opts } + ) + .once + .and_call_original - Ears::Setup.new.consumer(queue, MyConsumer, 2) + setup.consumer(arg_queue, consumer_class, 1, { custom: :opts }) end end end From e6f92fa866a656293a7158b1a7281a3f8ca384d9 Mon Sep 17 00:00:00 2001 From: Mike Blumtritt Date: Thu, 26 Oct 2023 13:26:24 +0200 Subject: [PATCH 5/8] Prettify --- lib/ears/setup.rb | 12 +++++----- spec/ears/setup_spec.rb | 50 ++++++++++++++++++++--------------------- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/lib/ears/setup.rb b/lib/ears/setup.rb index 48e254f..6484d3c 100644 --- a/lib/ears/setup.rb +++ b/lib/ears/setup.rb @@ -36,7 +36,7 @@ def queue(name, opts = {}) Bunny::Queue.new( Ears.channel, name, - queue_options(bunny_opts, retry_args) + queue_options(bunny_opts, retry_args), ) end @@ -72,7 +72,7 @@ def retry_arguments(name, opts) { 'x-dead-letter-exchange' => '', - 'x-dead-letter-routing-key' => "#{name}.retry" + 'x-dead-letter-routing-key' => "#{name}.retry", } end @@ -82,7 +82,7 @@ def create_consumer(queue, consumer_class, args, number) queue.channel, queue, "#{consumer_class.name}-#{number}", - args + args, ) end @@ -104,7 +104,7 @@ def create_retry_queue(name, delay, opts) Bunny::Queue.new( Ears.channel, "#{name}.retry", - opts.merge(retry_queue_opts(name, delay)) + opts.merge(retry_queue_opts(name, delay)), ) end @@ -113,8 +113,8 @@ def retry_queue_opts(name, delay) arguments: { 'x-message-ttl' => delay, 'x-dead-letter-exchange' => '', - 'x-dead-letter-routing-key' => name - } + 'x-dead-letter-routing-key' => name, + }, } end diff --git a/spec/ears/setup_spec.rb b/spec/ears/setup_spec.rb index 4569ac2..8642821 100644 --- a/spec/ears/setup_spec.rb +++ b/spec/ears/setup_spec.rb @@ -13,7 +13,7 @@ ears_channel, :type, :name, - :some_options + :some_options, ) setup.exchange(:name, :type, :some_options) @@ -28,15 +28,15 @@ { custom_argument: :forwared, arguments: { - 'x-custom-argument' => 'forwarded' - } - } + 'x-custom-argument' => 'forwarded', + }, + }, ) setup.queue( 'aName', custom_argument: :forwared, - arguments: { 'x-custom-argument' => 'forwarded' }.freeze + arguments: { 'x-custom-argument' => 'forwarded' }.freeze, ) end @@ -49,9 +49,9 @@ arguments: { 'x-dead-letter-exchange' => '', 'x-dead-letter-routing-key' => 'aName.retry', - 'x-custom-argument' => 'forwarded' - } - } + 'x-custom-argument' => 'forwarded', + }, + }, ) expect(Bunny::Queue).to receive(:new).with( @@ -62,9 +62,9 @@ arguments: { 'x-dead-letter-exchange' => '', 'x-dead-letter-routing-key' => 'aName', - 'x-message-ttl' => 311 - } - } + 'x-message-ttl' => 311, + }, + }, ) setup.queue( @@ -72,7 +72,7 @@ retry_queue: true, retry_delay: 311, custom_argument: :forwared, - arguments: { 'x-custom-argument' => 'forwarded' }.freeze + arguments: { 'x-custom-argument' => 'forwarded' }.freeze, ) end @@ -83,9 +83,9 @@ { custom_argument: :forwared, arguments: { - 'x-custom-argument' => 'forwarded' - } - } + 'x-custom-argument' => 'forwarded', + }, + }, ) expect(Bunny::Queue).to receive(:new).with( @@ -94,16 +94,16 @@ { custom_argument: :forwared, arguments: { - 'x-custom-argument' => 'forwarded' - } - } + 'x-custom-argument' => 'forwarded', + }, + }, ) setup.queue( 'aName', error_queue: true, custom_argument: :forwared, - arguments: { 'x-custom-argument' => 'forwarded' }.freeze + arguments: { 'x-custom-argument' => 'forwarded' }.freeze, ) end end @@ -117,8 +117,8 @@ Bunny::Queue, name: 'aQueue', options: { - queue: :options - } + queue: :options, + }, ) end let(:consumer_class) do @@ -130,7 +130,7 @@ Bunny::Queue, name: 'ConsumerQueue', channel: consumer_channel, - subscribe_with: nil + subscribe_with: nil, ) end let(:consumer_channel) do @@ -138,7 +138,7 @@ Bunny::Channel, prefetch: nil, on_uncaught_exception: nil, - number: 11 + number: 11, ) end @@ -164,7 +164,7 @@ arg_queue, consumer_class, 1, - { prefetch: 15, custom: :options } + { prefetch: 15, custom: :options }, ) end @@ -200,7 +200,7 @@ consumer_channel, consumer_queue, "#{consumer_class.name}-1", - { custom: :opts } + { custom: :opts }, ) .once .and_call_original From 89debff8932e25f5c39296bceb545b6495aee5c5 Mon Sep 17 00:00:00 2001 From: Mike Blumtritt Date: Thu, 26 Oct 2023 13:30:34 +0200 Subject: [PATCH 6/8] Undo useless change --- lib/ears.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/ears.rb b/lib/ears.rb index 9f1caed..545f55c 100644 --- a/lib/ears.rb +++ b/lib/ears.rb @@ -8,7 +8,6 @@ module Ears class << self # The global configuration for Ears. - # @attribute [r] configuration # @return [Ears::Configuration] attr_reader :configuration From ed12dbbc422295ac9429ae3dd292f74683cf19dd Mon Sep 17 00:00:00 2001 From: Mike Blumtritt Date: Thu, 26 Oct 2023 13:48:51 +0200 Subject: [PATCH 7/8] Tidy-up --- Rakefile | 2 +- lib/ears.rb | 11 ++++------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/Rakefile b/Rakefile index db90048..15f3b12 100644 --- a/Rakefile +++ b/Rakefile @@ -19,6 +19,6 @@ RuboCop::RakeTask.new(:rubocop) do |task| end desc 'Run Prettier' -task(:prettier) { system('npm run lint') } +task(:prettier) { sh 'npm run lint' } task default: %i[spec rubocop prettier] diff --git a/lib/ears.rb b/lib/ears.rb index 9f1caed..5f40015 100644 --- a/lib/ears.rb +++ b/lib/ears.rb @@ -8,9 +8,10 @@ module Ears class << self # The global configuration for Ears. - # @attribute [r] configuration # @return [Ears::Configuration] - attr_reader :configuration + def configuration + @configuration ||= Ears::Configuration.new + end # Yields the global configuration instance so you can modify it. # @yieldparam configuration [Ears::Configuration] The global configuration instance. @@ -72,9 +73,7 @@ def error!(error) # Used internally for testing. def reset! - @connection = nil - Thread.current[:ears_channel] = nil - @configuration = Ears::Configuration.new + @configuration = @connection = Thread.current[:ears_channel] = nil end private @@ -102,6 +101,4 @@ def connection_config }.compact end end - - reset! end From ba475be6ffaba30e2723c13d39de31d523082071 Mon Sep 17 00:00:00 2001 From: Mike Blumtritt Date: Thu, 26 Oct 2023 14:14:32 +0200 Subject: [PATCH 8/8] Correct coverage requirement --- spec/spec_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 3799230..09f9af6 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -13,5 +13,5 @@ if RSpec.configuration.files_to_run.length > 1 # Let's increase this later on - SimpleCov.minimum_coverage line: 97.4, branch: 62.8 + SimpleCov.minimum_coverage line: 97.17, branch: 62.8 end