euston-rabbitmq 1.0.1 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
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