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.
- checksums.yaml +4 -4
- data/.codeclimate.yml +50 -0
- data/.gitignore +1 -0
- data/.metrics +1 -5
- data/.reek +0 -1
- data/.rubocop.yml +2 -1
- data/.ruby-version +1 -1
- data/Guardfile +4 -0
- data/README.md +77 -61
- data/basquiat.gemspec +5 -4
- data/basquiat_docker.sh +1 -1
- data/docker/Dockerfile +8 -3
- data/docker-compose.yml +3 -3
- data/lib/basquiat/adapters/base_adapter.rb +42 -7
- data/lib/basquiat/adapters/base_message.rb +14 -9
- data/lib/basquiat/adapters/rabbitmq/configuration.rb +31 -16
- data/lib/basquiat/adapters/rabbitmq/connection.rb +33 -56
- data/lib/basquiat/adapters/rabbitmq/events.rb +1 -1
- data/lib/basquiat/adapters/rabbitmq/message.rb +10 -9
- data/lib/basquiat/adapters/rabbitmq/requeue_strategies/auto_acknowledge.rb +15 -0
- data/lib/basquiat/adapters/rabbitmq/requeue_strategies/base_strategy.rb +8 -4
- data/lib/basquiat/adapters/rabbitmq/requeue_strategies/basic_acknowledge.rb +1 -1
- data/lib/basquiat/adapters/rabbitmq/requeue_strategies/dead_lettering.rb +16 -15
- data/lib/basquiat/adapters/rabbitmq/requeue_strategies/delayed_delivery.rb +80 -16
- data/lib/basquiat/adapters/rabbitmq/requeue_strategies.rb +7 -0
- data/lib/basquiat/adapters/rabbitmq/session.rb +12 -14
- data/lib/basquiat/adapters/rabbitmq_adapter.rb +35 -16
- data/lib/basquiat/adapters/test_adapter.rb +2 -5
- data/lib/basquiat/errors/strategy_not_registered.rb +1 -9
- data/lib/basquiat/interfaces/base.rb +28 -7
- data/lib/basquiat/support/configuration.rb +33 -4
- data/lib/basquiat/support/hash_refinements.rb +9 -0
- data/lib/basquiat/support/json.rb +9 -0
- data/lib/basquiat/version.rb +1 -1
- data/lib/basquiat.rb +6 -1
- data/spec/lib/adapters/base_adapter_spec.rb +1 -1
- data/spec/lib/adapters/base_message_spec.rb +0 -6
- data/spec/lib/adapters/rabbitmq/configuration_spec.rb +2 -2
- data/spec/lib/adapters/rabbitmq/connection_spec.rb +8 -13
- data/spec/lib/adapters/rabbitmq/events_spec.rb +8 -1
- data/spec/lib/adapters/rabbitmq/requeue_strategies/auto_acknowledge_spec.rb +24 -0
- data/spec/lib/adapters/rabbitmq/requeue_strategies/basic_acknowledge_spec.rb +4 -4
- data/spec/lib/adapters/rabbitmq/requeue_strategies/dead_lettering_spec.rb +23 -28
- data/spec/lib/adapters/rabbitmq/requeue_strategies/delayed_delivery_spec.rb +105 -0
- data/spec/lib/adapters/rabbitmq_adapter_spec.rb +21 -17
- data/spec/lib/basquiat_spec.rb +0 -6
- data/spec/lib/interfaces/base_spec.rb +11 -19
- data/spec/lib/support/configuration_spec.rb +0 -8
- data/spec/lib/support/hash_refinements_spec.rb +2 -2
- data/spec/spec_helper.rb +2 -6
- data/spec/support/rabbitmq_queue_matchers.rb +9 -3
- metadata +21 -12
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'set'
|
2
2
|
|
3
3
|
module Basquiat
|
4
|
-
#
|
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
|
18
|
-
|
17
|
+
@adapter = Kernel.const_get Basquiat.configuration.default_adapter
|
18
|
+
adapter_options Basquiat.configuration.adapter_options
|
19
19
|
end
|
20
20
|
|
21
|
-
|
22
|
-
|
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
|
-
|
51
|
-
|
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::
|
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
|
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
|
data/lib/basquiat/version.rb
CHANGED
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
|
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
|
-
|
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(:
|
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(:
|
8
|
-
[
|
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(
|
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,
|
21
|
+
{ default_timeout: 0.2, max_retries: 2, connection_timeout: 0.3 }
|
27
22
|
end
|
28
23
|
|
29
|
-
before(:each) {
|
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(
|
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(
|
34
|
+
conn = connection.new(hosts: hosts, failover: failover)
|
40
35
|
expect { conn.start }.to_not raise_error
|
41
|
-
expect(conn.
|
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
|
-
{
|
8
|
-
port:
|
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.
|
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.
|
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
|
-
{
|
8
|
-
port:
|
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.
|
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('
|
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
|
-
|
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 '
|
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',
|
62
|
-
|
63
|
-
|
64
|
-
|
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 '
|
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:
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
adapter.subscribe_to('sample.message',
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
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
|