euston-rabbitmq 1.0.1 → 1.0.2

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.
Files changed (45) hide show
  1. data/Gemfile +2 -0
  2. data/Rakefile +0 -21
  3. data/euston-rabbitmq.gemspec +26 -37
  4. data/lib/euston-rabbitmq/{bindings → euston}/command_handler_binder.rb +2 -2
  5. data/lib/euston-rabbitmq/{errors.rb → euston/errors.rb} +1 -1
  6. data/lib/euston-rabbitmq/{bindings → euston}/event_handler_binder.rb +4 -4
  7. data/lib/euston-rabbitmq/{exchanges.rb → euston/exchanges.rb} +1 -1
  8. data/lib/euston-rabbitmq/{bindings → euston}/handler_binder.rb +2 -1
  9. data/lib/euston-rabbitmq/{queues.rb → euston/queues.rb} +1 -1
  10. data/lib/euston-rabbitmq/{subscriptions/retriable_subscription.rb → euston/retrying_subscription.rb} +17 -16
  11. data/lib/euston-rabbitmq/rabbitmq_client/queue.rb +70 -0
  12. data/lib/euston-rabbitmq/rabbitmq_client/reactive_message.rb +15 -0
  13. data/lib/euston-rabbitmq/{constant_loader.rb → reflection/constant_loader.rb} +0 -0
  14. data/lib/euston-rabbitmq/{handler_finder.rb → reflection/handler_finder.rb} +0 -0
  15. data/lib/euston-rabbitmq/{handler_reference.rb → reflection/handler_reference.rb} +0 -0
  16. data/lib/euston-rabbitmq/version.rb +1 -1
  17. data/lib/euston-rabbitmq.rb +8 -22
  18. data/spec/euston/command_handler_binder_spec.rb +24 -0
  19. data/spec/euston/event_handler_binder_spec.rb +36 -0
  20. data/spec/euston/exchanges_spec.rb +27 -0
  21. data/spec/euston/queues_spec.rb +24 -0
  22. data/spec/euston/retrying_subscription_spec.rb +74 -0
  23. data/spec/rabbitmq_client/queue_spec.rb +69 -0
  24. data/spec/{constant_loader_spec.rb → reflection/constant_loader_spec.rb} +3 -1
  25. data/spec/{handler_finder_spec.rb → reflection/handler_finder_spec.rb} +3 -1
  26. data/spec/spec_helper.rb +19 -52
  27. data/spec/support/filters.rb +10 -0
  28. data/spec/support/queue_subscription_thread_harness.rb +29 -0
  29. data/spec/support/rabbitmqadmin.rb +74 -0
  30. metadata +58 -117
  31. data/lib/euston-rabbitmq/command_handlers/retry_failed_message.rb +0 -29
  32. data/lib/euston-rabbitmq/event_handlers/message_failure.rb +0 -27
  33. data/lib/euston-rabbitmq/message_buffer.rb +0 -67
  34. data/lib/euston-rabbitmq/message_logger.rb +0 -50
  35. data/lib/euston-rabbitmq/queue.rb +0 -30
  36. data/lib/euston-rabbitmq/read_model/failed_message.rb +0 -36
  37. data/lib/euston-rabbitmq/read_model/message_buffer.rb +0 -57
  38. data/lib/euston-rabbitmq/read_model/message_log.rb +0 -37
  39. data/spec/command_buffer_spec.rb +0 -69
  40. data/spec/event_buffer_spec.rb +0 -69
  41. data/spec/exchange_declaration_spec.rb +0 -28
  42. data/spec/message_failure_spec.rb +0 -77
  43. data/spec/mt_safe_queue_subscription_spec.rb +0 -72
  44. data/spec/safe_queue_subscription_spec.rb +0 -50
  45. data/spec/support/factories.rb +0 -18
@@ -0,0 +1,24 @@
1
+ require File.expand_path('../../spec_helper.rb', __FILE__)
2
+ if jruby?
3
+
4
+ module CommandHandlerBinderTesting
5
+ class AbcHandler
6
+ end
7
+ end
8
+
9
+ describe 'command handler binder', :rabbitmq do
10
+ include Euston::RabbitMq::Exchanges
11
+ include Euston::RabbitMq::Queues
12
+
13
+ let(:handler) { CommandHandlerBinderTesting::AbcHandler }
14
+ let(:handler_finder) { Euston::RabbitMq::HandlerFinder.new.tap { |f| f.namespaces.push 'CommandHandlerBinderTesting' } }
15
+ let(:binder) { Euston::RabbitMq::CommandHandlerBinder.new handler_finder.find }
16
+ let(:bindings) { @rabbitmqadmin.list_bindings AMQP_OPTS[:vhost] }
17
+
18
+ before { binder.ensure_bindings_exist }
19
+
20
+ subject { bindings }
21
+
22
+ it { should satisfy { |bindings| bindings.any? { |b| b[:routing_key] == 'commands.abc_handler' && b[:destination] == 'command_handlers' } } }
23
+ end
24
+ end
@@ -0,0 +1,36 @@
1
+ require File.expand_path('../../spec_helper.rb', __FILE__)
2
+ if jruby?
3
+
4
+ module EventHandlerBinderTesting
5
+ class XyzHandler
6
+ include Euston::EventHandler
7
+
8
+ consumes :action_abc, 1 do |headers, event| end
9
+ consumes :action_xyz, 1 do |headers, event| end
10
+ end
11
+
12
+ class ZzzHandler
13
+ include Euston::EventHandler
14
+
15
+ consumes :action_abc, 1 do |headers, event| end
16
+ end
17
+ end
18
+
19
+ describe 'event handler binder', :rabbitmq do
20
+ include Euston::RabbitMq::Exchanges
21
+ include Euston::RabbitMq::Queues
22
+
23
+ let(:handler) { EventHandlerBinderTesting::XyzHandler }
24
+ let(:handler_finder) { Euston::RabbitMq::HandlerFinder.new.tap { |f| f.namespaces.push 'EventHandlerBinderTesting' } }
25
+ let(:binder) { Euston::RabbitMq::EventHandlerBinder.new handler_finder.find }
26
+ let(:bindings) { @rabbitmqadmin.list_bindings AMQP_OPTS[:vhost] }
27
+
28
+ before { binder.ensure_bindings_exist }
29
+
30
+ subject { bindings }
31
+
32
+ it { should satisfy { |bindings| bindings.any? { |b| b[:routing_key] == 'events.action_abc' && b[:destination] == 'xyz_handler' } } }
33
+ it { should satisfy { |bindings| bindings.any? { |b| b[:routing_key] == 'events.action_xyz' && b[:destination] == 'xyz_handler' } } }
34
+ it { should satisfy { |bindings| bindings.any? { |b| b[:routing_key] == 'events.action_abc' && b[:destination] == 'zzz_handler' } } }
35
+ end
36
+ end
@@ -0,0 +1,27 @@
1
+ require File.expand_path('../../spec_helper.rb', __FILE__)
2
+ if jruby?
3
+
4
+ describe 'exchanges', :rabbitmq do
5
+ include Euston::RabbitMq::Exchanges
6
+
7
+ context 'creating the commands exchange' do
8
+ subject { get_exchange @channel, :commands }
9
+ its(:name) { should == 'commands' }
10
+ end
11
+
12
+ context 'creating the events exchange' do
13
+ subject { get_exchange @channel, :events }
14
+ its(:name) { should == 'events' }
15
+ end
16
+
17
+ context 'obtaining the same exchange instance' do
18
+ let(:name) { "exchange_#{Time.now.to_i}" }
19
+ let(:instance_1) { get_exchange @channel, name }
20
+ let(:instance_2) { get_exchange @channel, name }
21
+
22
+ subject { instance_1 }
23
+
24
+ it { should equal instance_2 }
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,24 @@
1
+ require File.expand_path('../../spec_helper.rb', __FILE__)
2
+ if jruby?
3
+
4
+ describe 'queues', :rabbitmq do
5
+ include Euston::RabbitMq::Queues
6
+
7
+ let(:name) { "queue_#{Time.now.to_i}" }
8
+
9
+ context 'creating a queue' do
10
+ subject { get_queue @channel, name }
11
+
12
+ its(:name) { should == name }
13
+ end
14
+
15
+ context 'obtaining the same queue instance' do
16
+ let(:instance_1) { get_queue(@channel, name) }
17
+ let(:instance_2) { get_queue(@channel, name) }
18
+
19
+ subject { instance_1 }
20
+
21
+ it { should equal instance_2 }
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,74 @@
1
+ require File.expand_path('../../spec_helper.rb', __FILE__)
2
+ if jruby?
3
+
4
+ describe 'retrying subscription', :rabbitmq do
5
+ include Euston::RabbitMq::Exchanges
6
+ include Euston::RabbitMq::Queues
7
+
8
+ let(:failing_queue_name) { :failing_handler }
9
+ let(:failing_message_queue_name) { :failed_message_handler }
10
+ let(:events_exchange) { get_exchange @channel, :events }
11
+ let(:failing_handler_queue) { get_queue @channel, failing_queue_name }
12
+ let(:failed_message_handler_queue) { get_queue @channel, failing_message_queue_name }
13
+ let(:failing_message_handler_queue_harness) { QueueSubscriptionThreadHarness.new failed_message_handler_queue }
14
+ let(:outcomes) { { :attempts_to_handle => 0, :failure => nil } }
15
+ let(:message) { { :x => 123 } }
16
+ let(:routing_key) { 'events.failing' }
17
+ let(:error) { 'kablammo' }
18
+
19
+ before do
20
+ failing_handler_queue.bind events_exchange, :routing_key => routing_key
21
+ failed_message_handler_queue.bind events_exchange, :routing_key => 'events.message_failed'
22
+
23
+ @thread = Thread.new(failing_queue_name, fail_limit, outcomes) do |name, fail_limit, outcomes|
24
+ channel = AMQP::Channel.new
25
+ subscription = Euston::RabbitMq::RetryingSubscription.new channel, name
26
+ subscription.when(:message_received => ->(body) { outcomes[:attempts_to_handle] += 1; raise error if outcomes[:attempts_to_handle] <= fail_limit })
27
+ subscription.subscribe
28
+ end
29
+
30
+ @thread.abort_on_exception = true
31
+ failing_message_handler_queue_harness.when(:message_received => ->(body) { outcomes[:failure] = body })
32
+ failing_message_handler_queue_harness.startup
33
+
34
+ events_exchange.publish ActiveSupport::JSON.encode(message), { :key => routing_key }
35
+
36
+ sleep 0.25
37
+ end
38
+
39
+ after do
40
+ failing_message_handler_queue_harness.shutdown
41
+ @thread.kill
42
+ end
43
+
44
+ subject { outcomes }
45
+
46
+ context 'when the message fails once but then succeeds on retry' do
47
+ let (:fail_limit) { 1 }
48
+ its([:attempts_to_handle]) { should == 2 }
49
+ end
50
+
51
+ context 'when the message fails twice but then succeeds on retry' do
52
+ let (:fail_limit) { 2 }
53
+ its([:attempts_to_handle]) { should == 3 }
54
+ end
55
+
56
+ context 'when the message fails 3 times' do
57
+ let (:fail_limit) { 3 }
58
+ its([:attempts_to_handle]) { should == 3 }
59
+
60
+ describe 'message failure handler' do
61
+ subject { outcomes[:failure] }
62
+ it { should_not be_nil }
63
+
64
+ describe 'body' do
65
+ subject { outcomes[:failure][:body] }
66
+ its([:message]) { should == message }
67
+ its([:routing_key]) { should == routing_key }
68
+ its([:error]) { should == error }
69
+ its([:backtrace]) { should_not be_nil }
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,69 @@
1
+ require File.expand_path('../../spec_helper.rb', __FILE__)
2
+ if jruby?
3
+
4
+ PUBLISH_OPTS = { :immediate => false,
5
+ :mandatory => true,
6
+ :persistent => true,
7
+ :routing_key => 'messages.xyz' }
8
+
9
+ describe 'queue', :rabbitmq do
10
+ let(:queue) { @channel.queue "test-#{Time.now.to_i}", :durable => true, :nowait => false }
11
+
12
+ context 'queue api' do
13
+ subject { queue }
14
+
15
+ it { should respond_to(:safe_subscribe) }
16
+ end
17
+
18
+ describe 'subscription behaviour' do
19
+ def publish data
20
+ exchange.publish data, :immediate => false,
21
+ :mandatory => true,
22
+ :persistent => true,
23
+ :routing_key => 'messages.xyz'
24
+ sleep 0.25
25
+ end
26
+
27
+ let(:exchange) { @channel.topic "test-#{Time.now.to_i - 1}", :durable => true, :nowait => false }
28
+ let(:harness) { QueueSubscriptionThreadHarness.new queue }
29
+
30
+ before do
31
+ queue.bind exchange, :routing_key => 'messages.#'
32
+
33
+ harness.when(:thread_starting => ->{ Thread.current[:failures] = 0; Thread.current[:successes] = 0 },
34
+ :message_decode_failed => ->(body, error) { Thread.current[:failures] += 1 },
35
+ :message_failed => ->(body, error, reactive_message) { Thread.current[:failures] += 1 },
36
+ :message_received => ->(body) { if body[:a] == 'oops'
37
+ raise 'Something bad happened'
38
+ else
39
+ Thread.current[:successes] += 1
40
+ end })
41
+ harness.startup
42
+ end
43
+
44
+ after { harness.shutdown }
45
+
46
+ subject { harness.thread }
47
+
48
+ context 'a non-json message is received' do
49
+ before { publish ' *897d- -' }
50
+
51
+ its([:failures]) { should == 1}
52
+ end
53
+
54
+ context 'a json message is received' do
55
+ context 'the message is processed successfully' do
56
+ before { publish JSON.generate({ :a => :b }) }
57
+
58
+ its([:successes]) { should == 1 }
59
+ end
60
+
61
+ context 'the message generated an error in the handler' do
62
+ before { publish JSON.generate({ :a => 'oops' }) }
63
+
64
+ its([:failures]) { should == 1 }
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -1,4 +1,5 @@
1
- require File.expand_path('../spec_helper.rb', __FILE__)
1
+ require File.expand_path('../../spec_helper.rb', __FILE__)
2
+ if jruby?
2
3
 
3
4
  module ConstantLoaderTesting
4
5
  module SubNamespace
@@ -36,3 +37,4 @@ describe 'constant loader' do
36
37
  its([:miss]) { should == true }
37
38
  end
38
39
  end
40
+ end
@@ -1,4 +1,5 @@
1
- require File.expand_path('../spec_helper.rb', __FILE__)
1
+ require File.expand_path('../../spec_helper.rb', __FILE__)
2
+ if jruby?
2
3
 
3
4
  module HandlerFinderTesting
4
5
  module EmptyNamespace; end
@@ -46,3 +47,4 @@ describe 'handler finder' do
46
47
  it { should have(1).items }
47
48
  end
48
49
  end
50
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,65 +1,32 @@
1
- AMQP_OPTS = { :user => 'euston-rabbitmq-tester',
2
- :pass => 'password',
3
- :host => 'localhost',
4
- :vhost => '/euston-rabbitmq-test' }
5
-
6
1
  require 'ap'
7
2
  require 'euston-rabbitmq'
8
- require 'evented-spec' unless RUBY_PLATFORM == 'java'
9
- require 'faker'
10
- require_rel 'support'
11
3
 
12
- def trim_array_ends array
13
- array.shift
14
- array.pop
15
- array
4
+ def jruby?
5
+ RUBY_PLATFORM.to_s == 'java'
16
6
  end
17
7
 
18
- module EustonRmqSpec
19
- def self.included scope
20
- unless RUBY_PLATFORM == 'java'
21
- scope.send :include, EventedSpec::AMQPSpec
22
- scope.send :default_options, AMQP_OPTS
23
- scope.send :default_timeout, 60
24
- end
25
-
26
- scope.send :before, :each do
27
- vhosts = `rabbitmqctl list_vhosts`.split("\n")
28
- vhosts = trim_array_ends vhosts
29
- vhost = '/euston-rabbitmq-test'
30
-
31
- `rabbitmqctl delete_vhost #{vhost}` if vhosts.include? vhost
32
- `rabbitmqctl add_vhost #{vhost}`
8
+ Safely.configure do |config|
9
+ config.strategies = [ Safely::Strategy::Log ]
10
+ end
33
11
 
34
- users = `rabbitmqctl list_users`.split("\n")
35
- users = trim_array_ends users
36
- users = users.map { |u| u.split("\t").shift }
12
+ if jruby?
13
+ require_rel 'support'
37
14
 
38
- user, password = 'euston-rabbitmq-tester', 'password'
15
+ AMQP_OPTS = { :host => 'localhost',
16
+ :pass => 'password',
17
+ :user => 'euston-rabbitmq-test',
18
+ :vhost => 'euston-rabbitmq-test' }
39
19
 
40
- `rabbitmqctl delete_user #{user}` if users.include? user
41
- `rabbitmqctl add_user #{user} #{password}`
42
- `rabbitmqctl set_permissions -p #{vhost} #{user} ".*" ".*" ".*"`
43
- `rabbitmqctl set_permissions -p #{vhost} guest ".*" ".*" ".*"` if users.include? 'guest'
44
- end
20
+ RSpec.configure do |config|
21
+ config.treat_symbols_as_metadata_keys_with_true_values = true
45
22
 
46
- scope.send :before, :each do
47
- connection = Mongo::Connection.new('localhost', 27017)
48
- db = connection.db('euston-rabbitmq-test-event-store')
49
- db.collections.select { |c| c.name !~ /system/ }.each { |c| db.drop_collection c.name }
50
- Euston::RabbitMq.event_store_mongodb = db
23
+ config.before(:each, :amqp) do
24
+ initialize_amqp
51
25
  end
52
26
 
53
- if RUBY_PLATFORM == 'java'
54
- scope.send :before, :each do
55
- AMQP.settings.merge! AMQP_OPTS
56
- @channel = AMQP::Channel.new
57
- end
58
- else
59
- scope.send :amqp_before do
60
- @channel = AMQP::Channel.new
61
- @channel.should be_open
62
- end
27
+ config.before(:each, :rabbitmq) do
28
+ initialize_rabbitmq
29
+ initialize_amqp
63
30
  end
64
31
  end
65
- end
32
+ end
@@ -0,0 +1,10 @@
1
+ def initialize_amqp
2
+ AMQP.settings.merge! AMQP_OPTS
3
+ @channel = AMQP::Channel.new
4
+ end
5
+
6
+ def initialize_rabbitmq
7
+ @rabbitmqadmin = RabbitMqAdmin.new
8
+ @rabbitmqadmin.initialize_vhost AMQP_OPTS[:vhost]
9
+ @rabbitmqadmin.initialize_user AMQP_OPTS[:vhost], AMQP_OPTS[:user], AMQP_OPTS[:pass]
10
+ end
@@ -0,0 +1,29 @@
1
+ class QueueSubscriptionThreadHarness
2
+ include Hollywood
3
+
4
+ def initialize queue
5
+ @queue = queue
6
+ end
7
+
8
+ attr_reader :thread
9
+
10
+ def startup
11
+ cb = Hollywood.instance_method(:callback).bind self
12
+
13
+ @thread = Thread.new(cb, @queue) do |cb, queue|
14
+ cb.call :thread_starting
15
+
16
+ queue.when(:message_decode_failed => ->(body, error) { cb.call :message_decode_failed, body, error },
17
+ :message_failed => ->(body, error, reactive_message) { cb.call :message_failed, body, error, reactive_message },
18
+ :message_received => ->(body) { cb.call :message_received, body })
19
+
20
+ queue.safe_subscribe
21
+ end
22
+
23
+ @thread.abort_on_exception = true
24
+ end
25
+
26
+ def shutdown
27
+ @thread.kill
28
+ end
29
+ end
@@ -0,0 +1,74 @@
1
+ class RabbitMqAdmin
2
+ def initialize admin_user = 'guest', admin_password = 'guest'
3
+ @admin_user = admin_user
4
+ @admin_password = admin_password
5
+ end
6
+
7
+ def delete_exchanges
8
+ list_exchanges.each { |exchange| `#{run vhost} delete exchange name=#{exchange[:name]}` }
9
+ end
10
+
11
+ def delete_queues
12
+ list_queues.each { |queue| `#{run vhost} delete queue name=#{queue[:name]}` }
13
+ end
14
+
15
+ def initialize_user vhost, user, password, tags = ''
16
+ `#{run} delete user name=#{user}` if user_exists? user
17
+ `#{run} declare user name=#{user} password=#{password} tags=#{tags}`
18
+ add_user_to_vhost vhost, user
19
+ end
20
+
21
+ def initialize_vhost vhost
22
+ `#{run} delete vhost name=#{vhost}` if vhost_exists? vhost
23
+ `#{run} declare vhost name=#{vhost}`
24
+ add_user_to_vhost vhost, @admin_user
25
+ end
26
+
27
+ def run vhost = nil
28
+ vhost_arg = vhost.nil? ? '' : "-V #{vhost}"
29
+
30
+ "rabbitmqadmin -u #{@admin_user} -p #{@admin_password} -f raw_json #{vhost_arg}"
31
+ end
32
+
33
+ def list_bindings vhost
34
+ parse `#{run vhost} list bindings`
35
+ end
36
+
37
+ def list_exchanges vhost
38
+ parse `#{run vhost} list exchanges`
39
+ end
40
+
41
+ def list_queues vhost
42
+ parse `#{run vhost} list queues`
43
+ end
44
+
45
+ def list_users
46
+ parse `#{run} list users`
47
+ end
48
+
49
+ def list_vhosts
50
+ parse `#{run} list vhosts`
51
+ end
52
+
53
+ def purge_queues
54
+ list_queues.each { |queue| `#{run vhost} purge queue name=#{queue[:name]}` }
55
+ end
56
+
57
+ def user_exists? user
58
+ list_users.any? { |u| u[:name] == user }
59
+ end
60
+
61
+ def vhost_exists? vhost
62
+ list_vhosts.any? { |h| h[:name] == vhost }
63
+ end
64
+
65
+ private
66
+
67
+ def parse json
68
+ JSON.parse json, :symbolize_names => true
69
+ end
70
+
71
+ def add_user_to_vhost vhost, user
72
+ `#{run} -V #{vhost} declare permission vhost=#{vhost} user=#{user} configure=\"\.\*\" write=\"\.\*\" read=\"\.\*\"`
73
+ end
74
+ end