basquiat 1.2.0 → 1.3.0.pre.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|