message-driver 0.6.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +20 -2
  3. data/.rubocop_todo.yml +15 -23
  4. data/.travis.yml +10 -22
  5. data/CHANGELOG.md +9 -0
  6. data/Gemfile +34 -24
  7. data/Guardfile +46 -29
  8. data/LICENSE +1 -1
  9. data/Rakefile +14 -6
  10. data/features/CHANGELOG.md +1 -0
  11. data/features/step_definitions/logging_steps.rb +3 -2
  12. data/features/support/firewall_helper.rb +2 -2
  13. data/features/support/no_error_matcher.rb +1 -1
  14. data/lib/message_driver/adapters/base.rb +115 -11
  15. data/lib/message_driver/adapters/bunny_adapter.rb +58 -46
  16. data/lib/message_driver/adapters/in_memory_adapter.rb +57 -35
  17. data/lib/message_driver/adapters/stomp_adapter.rb +10 -10
  18. data/lib/message_driver/broker.rb +16 -19
  19. data/lib/message_driver/client.rb +3 -7
  20. data/lib/message_driver/destination.rb +4 -4
  21. data/lib/message_driver/message.rb +3 -2
  22. data/lib/message_driver/middleware/block_middleware.rb +1 -1
  23. data/lib/message_driver/subscription.rb +1 -1
  24. data/lib/message_driver/version.rb +1 -1
  25. data/message-driver.gemspec +6 -6
  26. data/spec/integration/bunny/amqp_integration_spec.rb +6 -4
  27. data/spec/integration/bunny/bunny_adapter_spec.rb +1 -3
  28. data/spec/integration/in_memory/in_memory_adapter_spec.rb +46 -6
  29. data/spec/integration/stomp/stomp_adapter_spec.rb +0 -2
  30. data/spec/spec_helper.rb +6 -0
  31. data/spec/support/matchers/override_method_matcher.rb +7 -0
  32. data/spec/support/shared/adapter_examples.rb +3 -0
  33. data/spec/support/shared/client_ack_examples.rb +26 -4
  34. data/spec/support/shared/context_examples.rb +46 -0
  35. data/spec/support/shared/destination_examples.rb +28 -0
  36. data/spec/support/shared/subscription_examples.rb +6 -1
  37. data/spec/support/shared/transaction_examples.rb +35 -4
  38. data/spec/support/test_adapter.rb +19 -0
  39. data/spec/support/utils.rb +1 -5
  40. data/spec/units/message_driver/adapters/base_spec.rb +37 -31
  41. data/spec/units/message_driver/broker_spec.rb +1 -2
  42. data/spec/units/message_driver/client_spec.rb +3 -3
  43. data/spec/units/message_driver/destination_spec.rb +4 -2
  44. data/spec/units/message_driver/message_spec.rb +9 -3
  45. data/test_lib/broker_config.rb +0 -2
  46. data/test_lib/provider/base.rb +2 -6
  47. data/test_lib/provider/rabbitmq.rb +3 -3
  48. metadata +18 -16
  49. data/ci/travis_setup +0 -7
  50. data/features/CHANGELOG.md +0 -102
@@ -129,7 +129,6 @@ module MessageDriver
129
129
  end
130
130
 
131
131
  describe '#create_destination' do
132
-
133
132
  context 'with defaults' do
134
133
  context 'the resulting destination' do
135
134
  let(:dest_name) { 'my_dest' }
@@ -327,7 +326,6 @@ module MessageDriver
327
326
  adapter_context.create_destination(dest_name, type: :exchange, declare: { auto_delete: true })
328
327
  end.to raise_error MessageDriver::Error, /you must provide a valid exchange type/
329
328
  end
330
-
331
329
  end
332
330
 
333
331
  context 'and bindings are provided' do
@@ -363,7 +361,7 @@ module MessageDriver
363
361
  it 'raises in an error' do
364
362
  expect do
365
363
  adapter_context.create_destination('my_dest', type: :foo_bar)
366
- end.to raise_error MessageDriver::Error, "invalid destination type #{:foo_bar}"
364
+ end.to raise_error MessageDriver::Error, 'invalid destination type foo_bar'
367
365
  end
368
366
  end
369
367
  end
@@ -5,8 +5,10 @@ require 'message_driver/adapters/in_memory_adapter'
5
5
  module MessageDriver
6
6
  module Adapters
7
7
  RSpec.describe InMemoryAdapter, :in_memory, type: :integration do
8
- let(:broker) { double(Broker) }
9
- subject(:adapter) { described_class.new(broker) }
8
+ let(:broker) { Broker.configure(adapter: :in_memory) }
9
+ subject(:adapter) { broker.adapter }
10
+
11
+ it { is_expected.to be_a InMemoryAdapter }
10
12
 
11
13
  describe '#new_context' do
12
14
  it 'returns a InMemoryAdapter::InMemoryContext' do
@@ -70,9 +72,8 @@ module MessageDriver
70
72
  adapter.reset_after_tests
71
73
 
72
74
  destinations.each do |destination|
73
- expect(destination.subscription).to be_nil
75
+ expect(destination.subscriptions).to be_empty
74
76
  end
75
-
76
77
  end
77
78
  end
78
79
 
@@ -87,7 +88,7 @@ module MessageDriver
87
88
  dest1.subscribe(&consumer)
88
89
  end
89
90
  it 'is the same consumer on the other destination' do
90
- expect(dest2.subscription.consumer).to be(consumer)
91
+ expect(dest2.subscriptions.first.consumer).to be(consumer)
91
92
  end
92
93
  end
93
94
 
@@ -123,7 +124,46 @@ module MessageDriver
123
124
  describe 'subscribing a consumer' do
124
125
  let(:destination) { adapter.create_destination(:my_queue) }
125
126
 
126
- let(:subscription_type) { MessageDriver::Adapters::InMemoryAdapter::Subscription }
127
+ context 'when there are already messages on the queue' do
128
+ it 'sends those initial messages to the first subscription created' do
129
+ 4.times { destination.publish('a message') }
130
+ msgs1 = []
131
+ msgs2 = []
132
+
133
+ destination.subscribe do |msg|
134
+ msgs1 << msg
135
+ end
136
+
137
+ destination.subscribe do |msg|
138
+ msgs2 << msg
139
+ end
140
+
141
+ aggregate_failures do
142
+ expect(msgs1.size).to eq(4)
143
+ expect(msgs2.size).to eq(0)
144
+ end
145
+ end
146
+ end
147
+
148
+ it 'supports multiple subscriptions on a given destination and distributes the messages between them' do
149
+ msgs1 = []
150
+ msgs2 = []
151
+
152
+ destination.subscribe do |msg|
153
+ msgs1 << msg
154
+ end
155
+
156
+ destination.subscribe do |msg|
157
+ msgs2 << msg
158
+ end
159
+
160
+ 4.times { destination.publish('a message') }
161
+
162
+ aggregate_failures do
163
+ expect(msgs1.size).to eq(2)
164
+ expect(msgs2.size).to eq(2)
165
+ end
166
+ end
127
167
  end
128
168
  end
129
169
  end
@@ -5,7 +5,6 @@ require 'message_driver/adapters/stomp_adapter'
5
5
  module MessageDriver
6
6
  module Adapters
7
7
  RSpec.describe StompAdapter, :stomp, type: :integration do
8
-
9
8
  let(:valid_connection_attrs) { BrokerConfig.config }
10
9
 
11
10
  describe '#initialize' do
@@ -113,7 +112,6 @@ module MessageDriver
113
112
  it_behaves_like 'subscriptions are not supported'
114
113
 
115
114
  describe '#create_destination' do
116
-
117
115
  context 'the resulting destination' do
118
116
  let(:dest_name) { '/queue/stomp_destination_spec' }
119
117
  subject(:destination) { adapter_context.create_destination(dest_name) }
data/spec/spec_helper.rb CHANGED
@@ -1,3 +1,9 @@
1
+ begin
2
+ require 'pry'
3
+ rescue LoadError
4
+ puts 'pry not loaded'
5
+ end
6
+
1
7
  ENV['COMMAND_NAME'] = 'specs'
2
8
  require File.join(File.dirname(__FILE__), '..', 'test_lib', 'coverage')
3
9
  require File.join(File.dirname(__FILE__), '..', 'test_lib', 'broker_config')
@@ -0,0 +1,7 @@
1
+ RSpec::Matchers.define :override_method do |expected|
2
+ match do |actual|
3
+ klass = actual.is_a?(Class) ? actual : actual.class
4
+ method = klass.instance_method(expected)
5
+ method.owner == klass
6
+ end
7
+ end
@@ -20,4 +20,7 @@ RSpec.shared_examples 'an adapter' do
20
20
  expect(subject.broker).to be(broker)
21
21
  end
22
22
  end
23
+
24
+ it { expect(subject.class).to respond_to(:new).with(1..2).arguments }
25
+ it { is_expected.to respond_to(:build_context).with(0).arguments }
23
26
  end
@@ -1,18 +1,40 @@
1
1
  RSpec.shared_examples 'client acks are supported' do
2
- describe '#supports_client_acks' do
2
+ describe '#supports_client_acks?' do
3
3
  it 'returns true' do
4
4
  expect(subject.supports_client_acks?).to eq(true)
5
5
  end
6
6
  end
7
7
 
8
- it { is_expected.to respond_to :ack_message }
9
- it { is_expected.to respond_to :nack_message }
8
+ it { is_expected.to override_method :handle_ack_message }
9
+ it { is_expected.not_to override_method :ack_message }
10
+ it { is_expected.to override_method :handle_nack_message }
11
+ it { is_expected.not_to override_method :nack_message }
10
12
  end
11
13
 
12
14
  RSpec.shared_examples 'client acks are not supported' do
13
- describe '#supports_client_acks' do
15
+ it { is_expected.not_to override_method :handle_ack_message }
16
+ it { is_expected.not_to override_method :handle_nack_message }
17
+ describe '#supports_client_acks?' do
14
18
  it 'returns false' do
15
19
  expect(subject.supports_client_acks?).to eq(false)
16
20
  end
17
21
  end
22
+
23
+ describe '#ack_message' do
24
+ it 'raises an error' do
25
+ message = double('message')
26
+ expect do
27
+ subject.ack_message(message)
28
+ end.to raise_error "#ack_message is not supported by #{subject.adapter.class}"
29
+ end
30
+ end
31
+
32
+ describe '#nack_message' do
33
+ it 'raises an error' do
34
+ message = double('message')
35
+ expect do
36
+ subject.nack_message(message)
37
+ end.to raise_error "#nack_message is not supported by #{subject.adapter.class}"
38
+ end
39
+ end
18
40
  end
@@ -5,6 +5,52 @@ RSpec.shared_examples 'an adapter context' do
5
5
  it { expect(subject.adapter).to be adapter }
6
6
  end
7
7
 
8
+ describe 'interface' do
9
+ it { is_expected.to respond_to(:create_destination).with(1..3).arguments }
10
+ it { is_expected.to respond_to(:handle_create_destination).with(1..3).arguments }
11
+
12
+ it { is_expected.to respond_to(:publish).with(2..4).arguments }
13
+ it { is_expected.to respond_to(:handle_publish).with(2..4).arguments }
14
+
15
+ it { is_expected.to respond_to(:pop_message).with(1..2).arguments }
16
+ it { is_expected.to respond_to(:handle_pop_message).with(1..2).arguments }
17
+
18
+ it { is_expected.to respond_to(:subscribe).with(1..2).arguments }
19
+ it { is_expected.to respond_to(:handle_subscribe).with(1..2).arguments }
20
+
21
+ it { is_expected.to respond_to(:ack_message).with(1..2).arguments }
22
+ it { is_expected.to respond_to(:handle_ack_message).with(1..2).arguments }
23
+ it { is_expected.to respond_to(:nack_message).with(1..2).arguments }
24
+ it { is_expected.to respond_to(:handle_nack_message).with(1..2).arguments }
25
+
26
+ it { is_expected.to respond_to(:begin_transaction).with(0..1).arguments }
27
+ it { is_expected.to respond_to(:handle_begin_transaction).with(0..1).arguments }
28
+ it { is_expected.to respond_to(:commit_transaction).with(0..1).arguments }
29
+ it { is_expected.to respond_to(:handle_commit_transaction).with(0..1).arguments }
30
+ it { is_expected.to respond_to(:rollback_transaction).with(0..1).arguments }
31
+ it { is_expected.to respond_to(:handle_rollback_transaction).with(0..1).arguments }
32
+ it { is_expected.to respond_to(:in_transaction?).with(0).arguments }
33
+
34
+ it { is_expected.to respond_to(:message_count).with(1).arguments }
35
+ it { is_expected.to respond_to(:handle_message_count).with(1).arguments }
36
+ it { is_expected.to respond_to(:consumer_count).with(1).arguments }
37
+ it { is_expected.to respond_to(:handle_consumer_count).with(1).arguments }
38
+
39
+ describe 'overrides' do
40
+ it { expect(subject.class).not_to override_method(:create_destination) }
41
+ it { expect(subject.class).to override_method(:handle_create_destination) }
42
+
43
+ it { expect(subject.class).not_to override_method(:publish) }
44
+ it { expect(subject.class).to override_method(:handle_publish) }
45
+
46
+ it { expect(subject.class).not_to override_method(:pop_message) }
47
+ it { expect(subject.class).to override_method(:handle_pop_message) }
48
+
49
+ it { expect(subject.class).not_to override_method(:message_count) }
50
+ it { expect(subject.class).not_to override_method(:consumer_count) }
51
+ end
52
+ end
53
+
8
54
  it 'is initially valid' do
9
55
  is_expected.to be_valid
10
56
  end
@@ -17,6 +17,14 @@ RSpec.shared_examples 'a destination' do
17
17
 
18
18
  it { is_expected.to be_a MessageDriver::Message::Base }
19
19
 
20
+ it 'has a reference to the context that fetched it' do
21
+ expect(message.ctx).to be_a MessageDriver::Adapters::ContextBase
22
+ end
23
+
24
+ it 'has a reference to the destination that it was fetched from' do
25
+ expect(message.destination).to be_a MessageDriver::Destination::Base
26
+ end
27
+
20
28
  describe '#body' do
21
29
  it { expect(subject.body).to eq(body) }
22
30
  end
@@ -30,6 +38,14 @@ RSpec.shared_examples 'a destination' do
30
38
  end
31
39
  end
32
40
  end
41
+
42
+ context 'interface' do
43
+ it { is_expected.to respond_to(:publish).with(1..3).arguments }
44
+ it { is_expected.to respond_to(:pop_message).with(0..1).arguments }
45
+ it { is_expected.to respond_to(:message_count).with(0).arguments }
46
+ it { is_expected.to respond_to(:subscribe).with(0..1).arguments }
47
+ it { is_expected.to respond_to(:consumer_count).with(0).arguments }
48
+ end
33
49
  end
34
50
 
35
51
  RSpec.shared_examples "doesn't support #message_count" do
@@ -50,6 +66,12 @@ RSpec.shared_examples 'supports #message_count' do
50
66
  pause_if_needed
51
67
  end.to change { destination.message_count }.by(2)
52
68
  end
69
+
70
+ it { is_expected.not_to override_method :message_count }
71
+
72
+ it 'the adapter context overrides #handle_message_count' do
73
+ expect(subject.adapter.broker.client.current_adapter_context).to override_method :handle_message_count
74
+ end
53
75
  end
54
76
 
55
77
  RSpec.shared_examples "doesn't support #consumer_count" do
@@ -79,4 +101,10 @@ RSpec.shared_examples 'supports #consumer_count' do
79
101
  end.to change { destination.consumer_count }.by(-2)
80
102
  end
81
103
  end
104
+
105
+ it { is_expected.not_to override_method :consumer_count }
106
+
107
+ it 'the adapter context overrides #handle_consumer_count' do
108
+ expect(subject.adapter.broker.client.current_adapter_context).to override_method :handle_message_count
109
+ end
82
110
  end
@@ -5,6 +5,8 @@ RSpec.shared_examples 'subscriptions are not supported' do
5
5
  end
6
6
  end
7
7
 
8
+ it { is_expected.not_to override_method(:handle_subscribe) }
9
+
8
10
  describe '#subscribe' do
9
11
  it 'raises an error' do
10
12
  destination = double('destination')
@@ -23,6 +25,8 @@ RSpec.shared_examples 'subscriptions are supported' do |subscription_type|
23
25
  end
24
26
  end
25
27
 
28
+ it { is_expected.to override_method(:handle_subscribe) }
29
+
26
30
  let(:destination) { adapter_context.create_destination('subscriptions_example_queue') }
27
31
 
28
32
  let(:message1) { 'message 1' }
@@ -87,6 +91,7 @@ RSpec.shared_examples 'subscriptions are supported' do |subscription_type|
87
91
  subscription
88
92
  pause_if_needed
89
93
  end.to change { messages.size }.from(0).to(2)
94
+ expect(messages.map(&:destination)).to all(be_a(MessageDriver::Destination::Base))
90
95
  bodies = messages.map(&:body)
91
96
  expect(bodies).to include(message1)
92
97
  expect(bodies).to include(message2)
@@ -121,7 +126,7 @@ RSpec.shared_examples 'subscriptions are supported' do |subscription_type|
121
126
  let(:error) { RuntimeError.new('oh nos!') }
122
127
  let(:consumer) do
123
128
  lambda do |_|
124
- fail error
129
+ raise error
125
130
  end
126
131
  end
127
132
 
@@ -4,6 +4,34 @@ RSpec.shared_examples 'transactions are not supported' do
4
4
  expect(subject.supports_transactions?).to eq(false)
5
5
  end
6
6
  end
7
+
8
+ describe '#begin_transaction' do
9
+ it 'raises an error' do
10
+ expect do
11
+ subject.begin_transaction
12
+ end.to raise_error "transactions are not supported by #{subject.adapter.class}"
13
+ end
14
+ end
15
+
16
+ describe '#commit_transaction' do
17
+ it 'raises an error' do
18
+ expect do
19
+ subject.commit_transaction
20
+ end.to raise_error "transactions are not supported by #{subject.adapter.class}"
21
+ end
22
+ end
23
+
24
+ describe '#rollback_transaction' do
25
+ it 'raises an error' do
26
+ expect do
27
+ subject.rollback_transaction
28
+ end.to raise_error "transactions are not supported by #{subject.adapter.class}"
29
+ end
30
+ end
31
+
32
+ it { is_expected.not_to override_method :handle_begin_transaction }
33
+ it { is_expected.not_to override_method :handle_commit_transaction }
34
+ it { is_expected.not_to override_method :handle_rollback_transaction }
7
35
  end
8
36
 
9
37
  RSpec.shared_examples 'transactions are supported' do
@@ -13,10 +41,13 @@ RSpec.shared_examples 'transactions are supported' do
13
41
  end
14
42
  end
15
43
 
16
- it { is_expected.to respond_to :begin_transaction }
17
- it { is_expected.to respond_to :commit_transaction }
18
- it { is_expected.to respond_to :rollback_transaction }
19
- it { is_expected.to respond_to :in_transaction? }
44
+ it { is_expected.to override_method :handle_begin_transaction }
45
+ it { is_expected.not_to override_method :begin_transaction }
46
+ it { is_expected.to override_method :handle_commit_transaction }
47
+ it { is_expected.not_to override_method :commit_transaction }
48
+ it { is_expected.to override_method :handle_rollback_transaction }
49
+ it { is_expected.not_to override_method :rollback_transaction }
50
+ it { is_expected.to override_method :in_transaction? }
20
51
 
21
52
  describe '#in_transaction?' do
22
53
  it "returns false if you aren't in a transaction" do
@@ -0,0 +1,19 @@
1
+ module MessageDriver
2
+ class TestAdapter < Adapters::Base
3
+ def initialize(broker, _config)
4
+ @broker = broker
5
+ end
6
+
7
+ def build_context
8
+ TestContext.new(self)
9
+ end
10
+ end
11
+
12
+ class TestContext < Adapters::ContextBase
13
+ def handle_create_destination(_name, _dest_options = nil, _message_props = nil); end
14
+
15
+ def handle_publish(_destination, _body, _dest_options = nil, _message_props = nil); end
16
+
17
+ def handle_pop_message(_destination, _options = nil); end
18
+ end
19
+ end
@@ -1,11 +1,7 @@
1
1
  module Utils
2
2
  def pause_if_needed(seconds = 0.1)
3
3
  seconds *= 10 if ENV['CI'] == 'true'
4
- case BrokerConfig.current_adapter
5
- when :in_memory
6
- else
7
- sleep seconds
8
- end
4
+ sleep seconds unless BrokerConfig.current_adapter == :in_memory
9
5
  end
10
6
  end
11
7
 
@@ -3,11 +3,12 @@ require 'spec_helper'
3
3
  module MessageDriver
4
4
  module Adapters
5
5
  RSpec.describe Base do
6
- class TestAdapter < Base
7
- def initialize(_configuration)
6
+ let(:spec_adapter_class) do
7
+ Class.new(described_class) do
8
+ def initialize; end
8
9
  end
9
10
  end
10
- subject(:adapter) { TestAdapter.new({}) }
11
+ subject(:adapter) { spec_adapter_class.new }
11
12
 
12
13
  describe '#new_context' do
13
14
  it 'raises an error' do
@@ -17,40 +18,45 @@ module MessageDriver
17
18
  end
18
19
  end
19
20
 
20
- describe ContextBase do
21
- class TestContext < ContextBase
22
- end
23
- subject(:adapter_context) { TestContext.new(adapter) }
24
-
25
- it_behaves_like 'an adapter context'
26
- it_behaves_like 'transactions are not supported'
27
- it_behaves_like 'client acks are not supported'
28
- it_behaves_like 'subscriptions are not supported'
29
-
30
- describe '#create_destination' do
31
- it 'raises an error' do
32
- expect do
33
- subject.create_destination('foo')
34
- end.to raise_error 'Must be implemented in subclass'
21
+ context 'with a test adapter' do
22
+ subject(:adapter) { TestAdapter.new(nil, {}) }
23
+
24
+ describe ContextBase do
25
+ context 'with a test context subclass' do
26
+ subject(:adapter_context) { TestContext.new(adapter) }
27
+
28
+ it_behaves_like 'an adapter context'
29
+ it_behaves_like 'transactions are not supported'
30
+ it_behaves_like 'client acks are not supported'
31
+ it_behaves_like 'subscriptions are not supported'
35
32
  end
36
- end
37
33
 
38
- describe '#publish' do
39
- it 'raises an error' do
40
- expect do
41
- subject.publish(:destination, foo: 'bar')
42
- end.to raise_error 'Must be implemented in subclass'
34
+ subject(:adapter_context) { ContextBase.new(adapter) }
35
+
36
+ describe '#create_destination' do
37
+ it 'raises an error' do
38
+ expect do
39
+ subject.create_destination('foo')
40
+ end.to raise_error 'Must be implemented in subclass'
41
+ end
43
42
  end
44
- end
45
43
 
46
- describe '#pop_message' do
47
- it 'raises an error' do
48
- expect do
49
- subject.pop_message(:destination)
50
- end.to raise_error 'Must be implemented in subclass'
44
+ describe '#publish' do
45
+ it 'raises an error' do
46
+ expect do
47
+ subject.publish(:destination, foo: 'bar')
48
+ end.to raise_error 'Must be implemented in subclass'
49
+ end
51
50
  end
52
- end
53
51
 
52
+ describe '#pop_message' do
53
+ it 'raises an error' do
54
+ expect do
55
+ subject.pop_message(:destination)
56
+ end.to raise_error 'Must be implemented in subclass'
57
+ end
58
+ end
59
+ end
54
60
  end
55
61
  end
56
62
  end