message-driver 0.6.1 → 0.7.0

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 (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