basquiat 1.2.0 → 1.3.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +50 -0
  3. data/.gitignore +1 -0
  4. data/.metrics +1 -5
  5. data/.reek +0 -1
  6. data/.rubocop.yml +2 -1
  7. data/.ruby-version +1 -1
  8. data/Guardfile +4 -0
  9. data/README.md +77 -61
  10. data/basquiat.gemspec +5 -4
  11. data/basquiat_docker.sh +1 -1
  12. data/docker/Dockerfile +8 -3
  13. data/docker-compose.yml +3 -3
  14. data/lib/basquiat/adapters/base_adapter.rb +42 -7
  15. data/lib/basquiat/adapters/base_message.rb +14 -9
  16. data/lib/basquiat/adapters/rabbitmq/configuration.rb +31 -16
  17. data/lib/basquiat/adapters/rabbitmq/connection.rb +33 -56
  18. data/lib/basquiat/adapters/rabbitmq/events.rb +1 -1
  19. data/lib/basquiat/adapters/rabbitmq/message.rb +10 -9
  20. data/lib/basquiat/adapters/rabbitmq/requeue_strategies/auto_acknowledge.rb +15 -0
  21. data/lib/basquiat/adapters/rabbitmq/requeue_strategies/base_strategy.rb +8 -4
  22. data/lib/basquiat/adapters/rabbitmq/requeue_strategies/basic_acknowledge.rb +1 -1
  23. data/lib/basquiat/adapters/rabbitmq/requeue_strategies/dead_lettering.rb +16 -15
  24. data/lib/basquiat/adapters/rabbitmq/requeue_strategies/delayed_delivery.rb +80 -16
  25. data/lib/basquiat/adapters/rabbitmq/requeue_strategies.rb +7 -0
  26. data/lib/basquiat/adapters/rabbitmq/session.rb +12 -14
  27. data/lib/basquiat/adapters/rabbitmq_adapter.rb +35 -16
  28. data/lib/basquiat/adapters/test_adapter.rb +2 -5
  29. data/lib/basquiat/errors/strategy_not_registered.rb +1 -9
  30. data/lib/basquiat/interfaces/base.rb +28 -7
  31. data/lib/basquiat/support/configuration.rb +33 -4
  32. data/lib/basquiat/support/hash_refinements.rb +9 -0
  33. data/lib/basquiat/support/json.rb +9 -0
  34. data/lib/basquiat/version.rb +1 -1
  35. data/lib/basquiat.rb +6 -1
  36. data/spec/lib/adapters/base_adapter_spec.rb +1 -1
  37. data/spec/lib/adapters/base_message_spec.rb +0 -6
  38. data/spec/lib/adapters/rabbitmq/configuration_spec.rb +2 -2
  39. data/spec/lib/adapters/rabbitmq/connection_spec.rb +8 -13
  40. data/spec/lib/adapters/rabbitmq/events_spec.rb +8 -1
  41. data/spec/lib/adapters/rabbitmq/requeue_strategies/auto_acknowledge_spec.rb +24 -0
  42. data/spec/lib/adapters/rabbitmq/requeue_strategies/basic_acknowledge_spec.rb +4 -4
  43. data/spec/lib/adapters/rabbitmq/requeue_strategies/dead_lettering_spec.rb +23 -28
  44. data/spec/lib/adapters/rabbitmq/requeue_strategies/delayed_delivery_spec.rb +105 -0
  45. data/spec/lib/adapters/rabbitmq_adapter_spec.rb +21 -17
  46. data/spec/lib/basquiat_spec.rb +0 -6
  47. data/spec/lib/interfaces/base_spec.rb +11 -19
  48. data/spec/lib/support/configuration_spec.rb +0 -8
  49. data/spec/lib/support/hash_refinements_spec.rb +2 -2
  50. data/spec/spec_helper.rb +2 -6
  51. data/spec/support/rabbitmq_queue_matchers.rb +9 -3
  52. metadata +21 -12
@@ -1,7 +1,7 @@
1
1
  require 'set'
2
2
 
3
3
  module Basquiat
4
- # base module extend the classes that will use the event infrastructure
4
+ # Base module used to extend the classes so that they will be able to use the event infrastructure
5
5
  module Base
6
6
  class << self
7
7
  def extended(klass)
@@ -14,41 +14,62 @@ module Basquiat
14
14
  end
15
15
 
16
16
  def reload_adapter_from_configuration
17
- @adapter = Kernel.const_get(Basquiat.configuration.default_adapter).new
18
- @adapter.adapter_options Basquiat.configuration.adapter_options
17
+ @adapter = Kernel.const_get Basquiat.configuration.default_adapter
18
+ adapter_options Basquiat.configuration.adapter_options
19
19
  end
20
20
 
21
- def event_adapter=(adapter)
22
- @adapter = adapter.new
21
+ # @!attribute [rw] adapter
22
+ # Initializes and return a instance of the default adapter specified on Basquiat.configuration.default_adapter
23
+ # @return [Basquiat::Adapter] the adapter instance for the current class
24
+ # @deprecated event_adapter is deprecated and will be removed eventually. Please use {#adapter}.
25
+ def adapter=(adapter_klass)
26
+ @adapter = adapter_klass.new
23
27
  end
28
+ alias_method :event_adapter=, :adapter=
24
29
 
25
30
  def adapter
26
31
  @adapter ||= Kernel.const_get(Basquiat.configuration.default_adapter).new
27
32
  end
28
33
 
34
+ # @param opts [Hash] The adapter specific options. Defaults to Basquiat.configuration.adapter_options
29
35
  def adapter_options(opts = Basquiat.configuration.adapter_options)
30
36
  adapter.adapter_options(opts)
31
37
  end
32
38
 
39
+ # Publishes the message of type event to the queue. Note that the message will be converted to a JSON
40
+ # @param event [String] the event name
41
+ # @param message [#to_json] Message to be JSONfied and sent to the Message Queue
33
42
  def publish(event, message)
34
43
  adapter.publish(event, message)
35
44
  end
36
45
 
46
+ # Subscribe the event with the proc passed.
47
+ # @param event_name [String] the event name
48
+ # @param proc [Symbol, #call] the proc to be executed when the event is consumed.
49
+ # You can pass anything that answers to call or a symbol.
50
+ # If a symbol is passed it will try to look for a public class method of the same name.
37
51
  def subscribe_to(event_name, proc)
38
52
  proc = make_callable(proc)
39
53
  adapter.subscribe_to(event_name, proc)
40
54
  end
41
55
 
56
+ # Utility method to force a disconnect from the message queue.
57
+ # @note The adapter should reconnect automatically.
42
58
  def disconnect
43
59
  adapter.disconnect
44
60
  end
45
61
 
62
+ # Utility method to check connection status
63
+ # @return [truthy, falsey]
46
64
  def connected?
47
65
  adapter.connected?
48
66
  end
49
67
 
50
- def listen(block: true)
51
- adapter.listen(block: block)
68
+ # Starts the consumer loop
69
+ # @param block [Boolean] If it should block the thread. The relevance of this is dictated by the adapter.
70
+ # Defaults to true.
71
+ def listen(block: true, rescue_proc: -> {})
72
+ adapter.listen(block: block, rescue_proc: rescue_proc)
52
73
  end
53
74
 
54
75
  private
@@ -1,6 +1,6 @@
1
- require 'basquiat/support/hash_refinements'
2
1
  require 'naught'
3
2
  require 'erb'
3
+ require 'basquiat/support/hash_refinements'
4
4
 
5
5
  module Basquiat
6
6
  require 'logger'
@@ -9,8 +9,32 @@ module Basquiat
9
9
  class Configuration
10
10
  using HashRefinements
11
11
 
12
+ def initialize
13
+ @yaml = {}
14
+ @rescue_proc = lambda do |exception, message|
15
+ logger.error do
16
+ { exception: exception, stack_trace: exception.stack_trace, message: message }.to_json
17
+ end
18
+ fail exception
19
+ end
20
+ end
21
+
22
+ # @!attribute queue_name
23
+ # @return [String] the queue name. Defaults to 'basquiat.queue'
24
+ # @!attribute exchange_name
25
+ # @return [String] the exchange name. Defaults to 'basquiat.exchange'
26
+ # @!attribute logger
27
+ # @return [Logger] return the application logger. Defaults to {DefaultLogger}.
28
+ # @!attribute environment
29
+ # @return [Symbol] return the set environment or the value of the 'BASQUIAT_ENV' environment variable
30
+ # or :development
12
31
  attr_writer :queue_name, :exchange_name, :logger, :environment
13
32
 
33
+ # @!attribute rescue_proc
34
+ # @return [#call] return the callable to be executed when some exception is thrown. The callable receives the
35
+ # exception and message
36
+ attr_accessor :rescue_proc
37
+
14
38
  def queue_name
15
39
  @queue_name || 'basquiat.queue'
16
40
  end
@@ -27,19 +51,24 @@ module Basquiat
27
51
  (@environment || ENV['BASQUIAT_ENV'] || 'development').to_sym
28
52
  end
29
53
 
54
+ # Loads a YAML file with the configuration options
55
+ # @param path [String] the path of the configuration file
30
56
  def config_file=(path)
31
57
  load_yaml(path)
32
58
  setup_basic_options
33
59
  end
34
60
 
61
+ # @return [Hash] return the configured adapter options. Defaults to an empty {::Hash}
35
62
  def adapter_options
36
63
  config.fetch(:adapter_options) { Hash.new }
37
64
  end
38
65
 
66
+ # @return [String] return the configured default adapter. Defaults to {Adapters::Test}
39
67
  def default_adapter
40
- config.fetch(:default_adapter) { Basquiat::Adapter::Test }
68
+ config.fetch(:default_adapter) { 'Basquiat::Adapters::Test' }
41
69
  end
42
70
 
71
+ # Used by the railtie. Forces the reconfiguration of all extended classes
43
72
  def reload_classes
44
73
  Basquiat::Base.descendants.each(&:reload_adapter_from_configuration)
45
74
  end
@@ -47,7 +76,7 @@ module Basquiat
47
76
  private
48
77
 
49
78
  def config
50
- @yaml.fetch(environment)
79
+ @yaml.fetch(environment, {})
51
80
  end
52
81
 
53
82
  def load_yaml(path)
@@ -55,7 +84,7 @@ module Basquiat
55
84
  end
56
85
 
57
86
  def setup_basic_options
58
- @queue_name ||= config.fetch(:queue_name) { 'basquiat.exchange' }
87
+ @queue_name ||= config.fetch(:queue_name) { 'basquiat.exchange' }
59
88
  @exchange_name ||= config.fetch(:exchange_name) { 'basquiat.queue' }
60
89
  end
61
90
  end
@@ -1,5 +1,14 @@
1
1
  module Basquiat
2
2
  module HashRefinements
3
+ # @!method deep_merge
4
+ # Merges self with other_hash recursively
5
+ # @param other_hash [Hash] hash to be merged into self
6
+ # @return [self]
7
+
8
+ # @!method symbolize_keys
9
+ # Symbolize all the keys in a given hash. Works with nested hashes
10
+ # @return [Hash] return other hash with the symbolized keys
11
+
3
12
  refine Hash do
4
13
  def deep_merge(other_hash)
5
14
  other_hash.each_pair do |key, value|
@@ -1,9 +1,18 @@
1
1
  module Basquiat
2
+ # A simple MultiJson wrapper to protect against eventual API changes.
2
3
  module Json
4
+ # Serializes an Object into a JSON
5
+ # @see MultiJson.dump
6
+ # @param object [Object] object to be serialized
7
+ # @return [String] JSON representation of the object
3
8
  def self.encode(object)
4
9
  MultiJson.dump(object)
5
10
  end
6
11
 
12
+ # De-serializes a JSON into a Hash
13
+ # @see MultiJson.load
14
+ # @param object [Object] object to be de-serialized
15
+ # @return [Hash] Hash representing the JSON. The keys are symbolized by default
7
16
  def self.decode(object)
8
17
  MultiJson.load(object, symbolize_keys: true)
9
18
  rescue MultiJson::ParseError
@@ -1,4 +1,4 @@
1
1
  # Version file
2
2
  module Basquiat
3
- VERSION = '1.2.0'
3
+ VERSION = '1.3.0-1'
4
4
  end
data/lib/basquiat.rb CHANGED
@@ -8,21 +8,26 @@ require 'basquiat/adapters'
8
8
  require 'basquiat/version'
9
9
  require 'basquiat/interfaces/base'
10
10
 
11
- # Overall namespace And config class
11
+ # Overall namespace config class
12
12
  module Basquiat
13
13
  class << self
14
+ # resets the gems configuration. Useful for testing and not much else
14
15
  def reset
15
16
  @configuration = Configuration.new
16
17
  end
17
18
 
19
+ # @return [Configuration] returns or initializes the Configuration object
18
20
  def configuration
19
21
  @configuration ||= Configuration.new
20
22
  end
21
23
 
24
+ # used to configure the gem using a block
25
+ # @yieldparam [Configuration] configuration the current {Configuration} instance
22
26
  def configure
23
27
  yield configuration
24
28
  end
25
29
 
30
+ # @return [Logger] shorthand for configuration.logger
26
31
  def logger
27
32
  configuration.logger
28
33
  end
@@ -11,7 +11,7 @@ describe Basquiat::Adapters::Base do
11
11
  end
12
12
 
13
13
  it 'raise error when using an unregistered strategy' do
14
- # expect(adapter.use_strategy(:not_here)).to raise_error StrategyNotRegistered
14
+ expect { adapter.class.strategy(:not_here) }.to raise_error Basquiat::Errors::StrategyNotRegistered
15
15
  end
16
16
 
17
17
  it 'register a requeue strategy' do
@@ -7,10 +7,4 @@ describe Basquiat::Adapters::BaseMessage do
7
7
  expect(message.fetch(:data)).to eq('everything is AWESOME!')
8
8
  expect { message.fetch(:error) }.to raise_error KeyError
9
9
  end
10
-
11
- [:ack?, :unack, :requeue, :delay_redelivery].each do |meth|
12
- it "raise a SubclassResponsibility error if #{meth} isn't implemented" do
13
- expect { message.public_send(meth) }.to raise_error Basquiat::Errors::SubclassResponsibility
14
- end
15
- end
16
10
  end
@@ -19,11 +19,11 @@ describe Basquiat::Adapters::RabbitMq::Configuration do
19
19
  end
20
20
 
21
21
  it '#connection_options' do
22
- expect(config.connection_options.keys).to contain_exactly(:servers, :auth, :failover)
22
+ expect(config.connection_options.keys).to contain_exactly(:hosts, :auth, :port)
23
23
  end
24
24
 
25
25
  it '#session_options' do
26
- expect(config.session_options.keys).to contain_exactly(:exchange, :queue, :publisher)
26
+ expect(config.session_options.keys).to contain_exactly(:exchange, :queue, :publisher, :consumer)
27
27
  end
28
28
 
29
29
  context 'Strategies' do
@@ -4,17 +4,12 @@ require 'basquiat/adapters/rabbitmq_adapter'
4
4
  describe Basquiat::Adapters::RabbitMq::Connection do
5
5
  subject(:connection) { Basquiat::Adapters::RabbitMq::Connection }
6
6
 
7
- let(:servers) do
8
- [{ host: ENV.fetch('BASQUIAT_RABBITMQ_1_PORT_5672_TCP_ADDR') { 'localhost' },
9
- port: ENV.fetch('BASQUIAT_RABBITMQ_1_PORT_5672_TCP_PORT') { 5672 } }]
10
- end
11
-
12
- before(:each) do
13
- Basquiat.configure { |c| c.logger = Logger.new('log/basquiat.log') }
7
+ let(:hosts) do
8
+ [ENV.fetch('BASQUIAT_RABBITMQ_1_PORT_5672_TCP_ADDR') { 'localhost' }]
14
9
  end
15
10
 
16
11
  it '#connected?' do
17
- conn = connection.new(servers: servers)
12
+ conn = connection.new(hosts: hosts)
18
13
  expect(conn.connected?).to be_falsey
19
14
  conn.start
20
15
  expect(conn.connected?).to_not be_truthy
@@ -23,22 +18,22 @@ describe Basquiat::Adapters::RabbitMq::Connection do
23
18
 
24
19
  context 'failover' do
25
20
  let(:failover) do
26
- { default_timeout: 0.2, max_retries: 2, threaded: false }
21
+ { default_timeout: 0.2, max_retries: 2, connection_timeout: 0.3 }
27
22
  end
28
23
 
29
- before(:each) { servers.unshift(host: 'localhost', port: 1234) }
24
+ before(:each) { hosts.unshift('172.168.0.124') }
30
25
 
31
26
  it 'tries a reconnection after a few seconds' do
32
- conn = connection.new(servers: [host: 'localhost', port: 1234],
27
+ conn = connection.new(hosts: ['172.168.0.124'],
33
28
  failover: { default_timeout: 0.2, max_retries: 1 })
34
29
  expect { conn.start }.to raise_error(Bunny::TCPConnectionFailed)
35
30
  conn.close
36
31
  end
37
32
 
38
33
  it 'uses another server after all retries on a single one' do
39
- conn = connection.new(servers: servers, failover: failover)
34
+ conn = connection.new(hosts: hosts, failover: failover)
40
35
  expect { conn.start }.to_not raise_error
41
- expect(conn.current_server_uri).to match "#{ENV.fetch('BASQUIAT_RABBITMQ_1_PORT_5672_TCP_PORT') { 5672 }}"
36
+ expect(conn.host).to match "#{ENV.fetch('BASQUIAT_RABBITMQ_1_PORT_5672_TCP_ADDR') { 'localhost' }}"
42
37
  conn.close
43
38
  end
44
39
  end
@@ -18,8 +18,15 @@ describe Basquiat::Adapters::RabbitMq::Events do
18
18
 
19
19
  context 'wildcard keys' do
20
20
  let(:proc) { -> { 'Hello from the lambda! o/' } }
21
+
21
22
  describe '*' do
22
23
  let(:words) { %w(awesome lame dumb cool) }
24
+
25
+ it 'event.* does not match event_some_word' do
26
+ events['event.*'] = proc
27
+ expect { events['event_some_word'] }.to raise_error KeyError
28
+ end
29
+
23
30
  context 'matches any ONE word' do
24
31
  it 'at the end' do
25
32
  events['some.event.*'] = proc
@@ -70,7 +77,7 @@ describe Basquiat::Adapters::RabbitMq::Events do
70
77
  %w(some.cool.event cool.event).each do |event|
71
78
  expect(events[event]).to eq(proc)
72
79
  end
73
- expect { events['event']}.to raise_error KeyError
80
+ expect { events['event'] }.to raise_error KeyError
74
81
  end
75
82
  end
76
83
  end
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+ require 'basquiat/adapters/rabbitmq_adapter'
3
+
4
+ describe Basquiat::Adapters::RabbitMq::AutoAcknowledge do
5
+ let(:adapter) { Basquiat::Adapters::RabbitMq.new }
6
+ let(:base_options) do
7
+ { connection: { hosts: [ENV.fetch('BASQUIAT_RABBITMQ_1_PORT_5672_TCP_ADDR') { 'localhost' }],
8
+ port: ENV.fetch('BASQUIAT_RABBITMQ_1_PORT_5672_TCP_PORT') { 5672 } },
9
+ publisher: { persistent: true },
10
+ requeue: { enabled: false } }
11
+ end
12
+
13
+ before(:each) do
14
+ adapter.adapter_options(base_options)
15
+ end
16
+
17
+ after(:each) { remove_queues_and_exchanges(adapter) }
18
+
19
+ it 'set manual_ack to false' do
20
+ # Setup the strategy
21
+ adapter.strategy
22
+ expect(adapter.send(:options)[:consumer][:manual_ack]).to be_falsey
23
+ end
24
+ end
@@ -4,8 +4,8 @@ require 'basquiat/adapters/rabbitmq_adapter'
4
4
  describe 'Requeue Strategies' do
5
5
  let(:adapter) { Basquiat::Adapters::RabbitMq.new }
6
6
  let(:base_options) do
7
- { servers: [{ host: ENV.fetch('BASQUIAT_RABBITMQ_1_PORT_5672_TCP_ADDR') { 'localhost' },
8
- port: ENV.fetch('BASQUIAT_RABBITMQ_1_PORT_5672_TCP_PORT') { 5672 } }],
7
+ { connection: { hosts: [ENV.fetch('BASQUIAT_RABBITMQ_1_PORT_5672_TCP_ADDR') { 'localhost' }],
8
+ port: ENV.fetch('BASQUIAT_RABBITMQ_1_PORT_5672_TCP_PORT') { 5672 } },
9
9
  publisher: { persistent: true } }
10
10
  end
11
11
 
@@ -18,7 +18,7 @@ describe 'Requeue Strategies' do
18
18
  adapter.listen(block: false)
19
19
 
20
20
  adapter.publish('some.event', data: 'stupid message')
21
- sleep 0.7 # Wait for the listening thread.
21
+ sleep 0.5 # Wait for the listening thread.
22
22
 
23
23
  expect(adapter.session.queue.message_count).to eq(0)
24
24
  expect(adapter.session.queue).to_not have_unacked_messages
@@ -29,7 +29,7 @@ describe 'Requeue Strategies' do
29
29
  adapter.listen(block: false)
30
30
 
31
31
  adapter.publish('some.event', data: 'stupid message')
32
- sleep 0.7 # Wait for the listening thread.
32
+ sleep 0.5 # Wait for the listening thread.
33
33
 
34
34
  expect(adapter.session.queue.message_count).to eq(0)
35
35
  expect(adapter.session.queue).to_not have_unacked_messages
@@ -4,28 +4,26 @@ require 'basquiat/adapters/rabbitmq_adapter'
4
4
  describe Basquiat::Adapters::RabbitMq::DeadLettering do
5
5
  let(:adapter) { Basquiat::Adapters::RabbitMq.new }
6
6
  let(:base_options) do
7
- { servers: [{ host: ENV.fetch('BASQUIAT_RABBITMQ_1_PORT_5672_TCP_ADDR') { 'localhost' },
8
- port: ENV.fetch('BASQUIAT_RABBITMQ_1_PORT_5672_TCP_PORT') { 5672 } }],
7
+ { connection: { hosts: [ENV.fetch('BASQUIAT_RABBITMQ_1_PORT_5672_TCP_ADDR') { 'localhost' }],
8
+ port: ENV.fetch('BASQUIAT_RABBITMQ_1_PORT_5672_TCP_PORT') { 5672 } },
9
9
  publisher: { persistent: true } }
10
10
  end
11
11
 
12
12
  before(:each) do
13
13
  adapter.adapter_options(base_options)
14
- adapter.class.register_strategy :dlx, Basquiat::Adapters::RabbitMq::DeadLettering
14
+ adapter.adapter_options(requeue: { enabled: true, strategy: 'dead_lettering' })
15
15
  end
16
16
 
17
17
  after(:each) { remove_queues_and_exchanges(adapter) }
18
18
 
19
19
  it 'creates the dead letter exchange' do
20
- adapter.adapter_options(requeue: { enabled: true, strategy: 'dlx' })
21
20
  adapter.strategy # initialize the strategy
22
21
  channel = adapter.session.channel
23
- expect(channel.exchanges.keys).to contain_exactly('my.test_exchange', 'basquiat.dlx')
22
+ expect(channel.exchanges.keys).to contain_exactly('basquiat.exchange', 'basquiat.dlx')
24
23
  end
25
24
 
26
25
  it 'creates and binds a dead letter queue' do
27
26
  # Initialize the strategy since we won't be listening to anything
28
- adapter.adapter_options(requeue: { enabled: true, strategy: 'dlx' })
29
27
  session = adapter.session
30
28
  adapter.strategy
31
29
 
@@ -33,7 +31,7 @@ describe Basquiat::Adapters::RabbitMq::DeadLettering do
33
31
  channel = session.channel
34
32
  expect(channel.queues.keys).to include('basquiat.dlq')
35
33
  expect(channel.queues['basquiat.dlq'].arguments)
36
- .to match(hash_including('x-dead-letter-exchange' => session.exchange.name, 'x-message-ttl' => 1000))
34
+ .to match(hash_including('x-dead-letter-exchange' => session.exchange.name, 'x-message-ttl' => 1000))
37
35
  expect(session.queue.arguments).to match(hash_including('x-dead-letter-exchange' => 'basquiat.dlx'))
38
36
 
39
37
  expect do
@@ -42,9 +40,8 @@ describe Basquiat::Adapters::RabbitMq::DeadLettering do
42
40
  end.to change { channel.queues['basquiat.dlq'].message_count }.by(1)
43
41
  end
44
42
 
45
- context 'if it was the queue that unacked the message then' do
43
+ context 'nacked the message from' do
46
44
  before(:each) do
47
- adapter.adapter_options(requeue: { enabled: true, strategy: 'dlx' })
48
45
  session = adapter.session
49
46
  adapter.strategy # initialize strategy
50
47
 
@@ -56,12 +53,13 @@ describe Basquiat::Adapters::RabbitMq::DeadLettering do
56
53
  end
57
54
  end
58
55
 
59
- it 'process the message' do
56
+ it 'this queue then process the message' do
60
57
  sample = 0
61
- adapter.subscribe_to('sample.message', ->(msg) do
62
- sample += 1
63
- sample == 3 ? msg.ack : msg.unack
64
- end)
58
+ adapter.subscribe_to('sample.message',
59
+ lambda do |msg|
60
+ sample += 1
61
+ sample == 3 ? msg.ack : msg.nack
62
+ end)
65
63
 
66
64
  adapter.listen(block: false)
67
65
  adapter.publish('sample.message', key: 'message')
@@ -70,24 +68,21 @@ describe Basquiat::Adapters::RabbitMq::DeadLettering do
70
68
  expect(sample).to eq(3)
71
69
  end
72
70
 
73
- it 'else drop the message' do
71
+ it 'another queue then drop the message' do
74
72
  ack_count = 0
75
73
  sample = 0
76
74
 
77
75
  other = Basquiat::Adapters::RabbitMq.new
78
- other.adapter_options(base_options.merge(queue: { name: 'other_queue' }, requeue: { enabled: true, strategy: 'dlx' }))
79
- other.subscribe_to('sample.message', ->(msg) do
80
- ack_count += 1
81
- end)
82
-
83
- adapter.subscribe_to('sample.message', ->(msg) do
84
- if sample == 3
85
- msg.ack
86
- else
87
- sample += 1;
88
- msg.unack
89
- end
90
- end)
76
+ other.adapter_options(base_options.merge(queue:
77
+ { name: 'other_queue' },
78
+ requeue: { enabled: true, strategy: 'dead_lettering', ttl: 5 }))
79
+ other.subscribe_to('sample.message', ->(_msg) { ack_count += 1 })
80
+
81
+ adapter.subscribe_to('sample.message',
82
+ lambda do |msg|
83
+ sample += 1
84
+ sample == 3 ? msg.ack : msg.nack
85
+ end)
91
86
 
92
87
  other.listen(block: false)
93
88
  adapter.listen(block: false)
@@ -0,0 +1,105 @@
1
+ require 'spec_helper'
2
+ require 'basquiat/adapters/rabbitmq_adapter'
3
+
4
+ describe Basquiat::Adapters::RabbitMq::DelayedDelivery do
5
+ let(:adapter) { Basquiat::Adapters::RabbitMq.new }
6
+ let(:base_options) do
7
+ { connection: { hosts: [ENV.fetch('BASQUIAT_RABBITMQ_1_PORT_5672_TCP_ADDR') { 'localhost' }],
8
+ port: ENV.fetch('BASQUIAT_RABBITMQ_1_PORT_5672_TCP_PORT') { 5672 } },
9
+ publisher: { persistent: true } }
10
+ end
11
+
12
+ before(:each) do
13
+ adapter.adapter_options(base_options)
14
+ adapter.class.register_strategy :ddl, Basquiat::Adapters::RabbitMq::DelayedDelivery
15
+ adapter.adapter_options(requeue: { enabled: true, strategy: 'ddl' })
16
+ adapter.strategy # initialize the strategy
17
+ end
18
+
19
+ after(:each) { remove_queues_and_exchanges(adapter) }
20
+
21
+ it 'creates the dead letter exchange' do
22
+ channel = adapter.session.channel
23
+ expect(channel.exchanges.keys).to contain_exactly('basquiat.exchange', 'basquiat.dlx')
24
+ end
25
+
26
+ it 'creates the timeout queues' do
27
+ channel = adapter.session.channel
28
+ expect(channel.queues.keys).to contain_exactly('basquiat.ddlq_1', 'basquiat.ddlq_2', 'basquiat.ddlq_4',
29
+ 'basquiat.ddlq_8', 'basquiat.ddlq_16', 'basquiat.queue',
30
+ 'basquiat.ddlq_rejected')
31
+ end
32
+
33
+ it 'set the message ttl and dead letter exchange for the delayed queues' do
34
+ session = adapter.session
35
+ channel = session.channel
36
+ expect(channel.queues['basquiat.ddlq_1'].arguments)
37
+ .to match(hash_including('x-dead-letter-exchange' => session.exchange.name, 'x-message-ttl' => 1_000))
38
+
39
+ expect(channel.queues['basquiat.ddlq_8'].arguments['x-message-ttl']).to eq(8_000)
40
+ end
41
+
42
+ it 'binds the delayed queues' do
43
+ session = adapter.session
44
+ channel = session.channel
45
+ expect do
46
+ channel.exchanges['basquiat.dlx'].publish({ data: 'some message' }.to_json, routing_key: '1000.some.event')
47
+ sleep 0.1
48
+ end.to change { channel.queues['basquiat.ddlq_1'].message_count }.by(1)
49
+ end
50
+
51
+ it 'associates the event *.queue_name.event.name with event.name' do
52
+ message = ''
53
+ session = adapter.session
54
+ adapter.subscribe_to('some.event', ->(msg) { message = msg[:data].upcase })
55
+
56
+ adapter.listen(block: false)
57
+ session.publish('1000.basquiat.queue.some.event', data: 'some message')
58
+ sleep 0.5
59
+
60
+ expect(message).to eq('SOME MESSAGE')
61
+ end
62
+
63
+ context 'when a message is requeued' do
64
+ it 'is republished with the appropriate routing key' do
65
+ session = adapter.session
66
+ adapter.subscribe_to('some.event', ->(msg) { msg.requeue })
67
+ adapter.listen(block: false)
68
+
69
+ expect do
70
+ session.publish('some.event', data: 'some message')
71
+ sleep 0.3
72
+ end.to change { session.channel.queues['basquiat.ddlq_1'].message_count }.by(1)
73
+ end
74
+
75
+ it 'goes to the reject queue if a requeue would exceed the maximum timeout' do
76
+ session = adapter.session
77
+ adapter.subscribe_to('some.event', ->(msg) { msg.requeue })
78
+ adapter.listen(block: false)
79
+
80
+ expect do
81
+ session.publish('32000.basquiat.queue.some.event', data: 'some message')
82
+ sleep 0.3
83
+ end.to change { session.channel.queues['basquiat.ddlq_rejected'].message_count }.by(1)
84
+ end
85
+
86
+ it 'after it expires it is reprocessed by the right queue' do
87
+ analysed = 0
88
+ session = adapter.session
89
+ adapter.subscribe_to('some.event',
90
+ lambda do |msg|
91
+ if analysed == 1
92
+ msg.ack
93
+ else
94
+ analysed += 1
95
+ msg.requeue
96
+ end
97
+ end)
98
+ adapter.listen(block: false)
99
+ session.publish('some.event', data: 'some message')
100
+ sleep 1.3
101
+ expect(analysed).to eq(1)
102
+ expect(session.queue).to_not have_unacked_messages
103
+ end
104
+ end
105
+ end