message-driver 0.4.0 → 0.5.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 (74) hide show
  1. checksums.yaml +4 -4
  2. data/.coveralls.yml +2 -0
  3. data/.rubocop.yml +26 -2
  4. data/.rubocop_todo.yml +15 -123
  5. data/.travis.yml +2 -1
  6. data/CHANGELOG.md +10 -1
  7. data/Gemfile +15 -6
  8. data/Rakefile +23 -12
  9. data/ci/reset_vhost +8 -3
  10. data/ci/travis_setup +0 -3
  11. data/features/.nav +6 -1
  12. data/features/CHANGELOG.md +10 -1
  13. data/features/amqp_specific_features/binding_amqp_destinations.feature +1 -0
  14. data/features/amqp_specific_features/declaring_amqp_exchanges.feature +1 -0
  15. data/features/amqp_specific_features/server_named_destinations.feature +1 -0
  16. data/features/destination_metadata.feature +26 -0
  17. data/features/logging.feature +1 -1
  18. data/features/middleware/middleware_basics.feature +91 -0
  19. data/features/middleware/middleware_ordering.feature +60 -0
  20. data/features/middleware/middleware_parameters.feature +43 -0
  21. data/features/middleware/middleware_with_blocks.feature +85 -0
  22. data/features/step_definitions/dynamic_destinations_steps.rb +1 -1
  23. data/features/step_definitions/message_consumers_steps.rb +5 -0
  24. data/features/step_definitions/middleware_steps.rb +10 -0
  25. data/features/step_definitions/steps.rb +10 -2
  26. data/features/support/env.rb +4 -3
  27. data/features/support/firewall_helper.rb +1 -1
  28. data/features/support/message_table_matcher.rb +3 -2
  29. data/features/support/no_error_matcher.rb +2 -2
  30. data/features/support/test_runner.rb +11 -57
  31. data/features/support/transforms.rb +12 -10
  32. data/lib/message_driver.rb +3 -1
  33. data/lib/message_driver/adapters/base.rb +11 -11
  34. data/lib/message_driver/adapters/bunny_adapter.rb +189 -132
  35. data/lib/message_driver/adapters/in_memory_adapter.rb +51 -34
  36. data/lib/message_driver/adapters/stomp_adapter.rb +22 -23
  37. data/lib/message_driver/broker.rb +21 -16
  38. data/lib/message_driver/client.rb +15 -16
  39. data/lib/message_driver/destination.rb +26 -8
  40. data/lib/message_driver/message.rb +5 -4
  41. data/lib/message_driver/middleware.rb +8 -0
  42. data/lib/message_driver/middleware/base.rb +19 -0
  43. data/lib/message_driver/middleware/block_middleware.rb +33 -0
  44. data/lib/message_driver/middleware/middleware_stack.rb +61 -0
  45. data/lib/message_driver/subscription.rb +2 -2
  46. data/lib/message_driver/version.rb +1 -1
  47. data/message-driver.gemspec +3 -4
  48. data/spec/integration/bunny/amqp_integration_spec.rb +21 -82
  49. data/spec/integration/bunny/bunny_adapter_spec.rb +288 -268
  50. data/spec/integration/in_memory/in_memory_adapter_spec.rb +93 -90
  51. data/spec/integration/stomp/stomp_adapter_spec.rb +126 -93
  52. data/spec/spec_helper.rb +11 -9
  53. data/spec/support/shared/adapter_examples.rb +1 -1
  54. data/spec/support/shared/client_ack_examples.rb +4 -4
  55. data/spec/support/shared/context_examples.rb +6 -4
  56. data/spec/support/shared/destination_examples.rb +54 -14
  57. data/spec/support/shared/subscription_examples.rb +33 -26
  58. data/spec/support/shared/transaction_examples.rb +12 -12
  59. data/spec/support/utils.rb +1 -1
  60. data/spec/units/message_driver/adapters/base_spec.rb +42 -40
  61. data/spec/units/message_driver/broker_spec.rb +38 -38
  62. data/spec/units/message_driver/client_spec.rb +87 -87
  63. data/spec/units/message_driver/destination_spec.rb +16 -11
  64. data/spec/units/message_driver/message_spec.rb +96 -70
  65. data/spec/units/message_driver/middleware/base_spec.rb +30 -0
  66. data/spec/units/message_driver/middleware/block_middleware_spec.rb +82 -0
  67. data/spec/units/message_driver/middleware/middleware_stack_spec.rb +165 -0
  68. data/spec/units/message_driver/subscription_spec.rb +18 -16
  69. data/test_lib/broker_config.rb +21 -5
  70. data/test_lib/coverage.rb +11 -0
  71. data/test_lib/provider/base.rb +59 -0
  72. data/test_lib/provider/in_memory.rb +6 -0
  73. data/test_lib/provider/rabbitmq.rb +67 -0
  74. metadata +46 -35
@@ -1,19 +1,24 @@
1
1
  require 'spec_helper'
2
2
 
3
- module MessageDriver::Destination
4
- describe Base do
5
- subject(:destination) { Base.new(nil, nil, nil, nil) }
3
+ module MessageDriver
4
+ module Destination
5
+ RSpec.describe Base do
6
+ subject(:destination) { Base.new(nil, nil, nil, nil) }
6
7
 
7
- it 'needs some real tests'
8
+ describe '#middlware' do
9
+ it { expect(subject.middleware).to be_a Middleware::MiddlewareStack }
10
+ end
8
11
 
9
- include_examples "doesn't support #message_count"
12
+ include_examples "doesn't support #message_count"
13
+ include_examples "doesn't support #consumer_count"
10
14
 
11
- describe '#subscribe' do
12
- it 'raises an error' do
13
- expect {
14
- consumer = lambda do |_| end
15
- destination.subscribe(&consumer)
16
- }.to raise_error "#subscribe is not supported by #{destination.class}"
15
+ describe '#subscribe' do
16
+ it 'raises an error' do
17
+ expect do
18
+ consumer = ->(_) {}
19
+ destination.subscribe(&consumer)
20
+ end.to raise_error "#subscribe is not supported by #{destination.class}"
21
+ end
17
22
  end
18
23
  end
19
24
  end
@@ -1,90 +1,116 @@
1
1
  require 'spec_helper'
2
2
 
3
- module MessageDriver::Message
4
- describe Base do
5
- describe '#initialize' do
6
- let(:body) { 'The message body' }
7
- let(:headers) { { foo: :bar, bar: :baz} }
8
- let(:properties) { {persistent: true, client_ack: true} }
9
- let(:ctx) { double('adapter_context') }
3
+ module MessageDriver
4
+ module Message
5
+ RSpec.describe Base do
6
+ describe '#initialize' do
7
+ let(:body) { 'The message body' }
8
+ let(:headers) { { foo: :bar, bar: :baz } }
9
+ let(:properties) { { persistent: true, client_ack: true } }
10
+ let(:ctx) { double('adapter_context') }
10
11
 
11
- context 'sets the body, header and properites on initialization' do
12
- subject { described_class.new(ctx, body, headers, properties) }
12
+ context 'sets the body, header and properites on initialization' do
13
+ subject { described_class.new(ctx, body, headers, properties) }
13
14
 
14
- its(:ctx) { should be(ctx) }
15
- its(:body) { should eq(body) }
16
- its(:headers) { should eq(headers) }
17
- its(:properties) { should eq(properties) }
18
- end
19
- end
15
+ describe '#ctx' do
16
+ it { expect(subject.ctx).to be(ctx) }
17
+ end
20
18
 
21
- let(:logger) { MessageDriver.logger }
22
- let(:ctx) { double('adapter_context') }
23
- let(:options) { double('options') }
24
- subject(:message) { described_class.new(ctx, 'body', {}, {}) }
19
+ describe '#body' do
20
+ it { expect(subject.body).to eq(body) }
21
+ end
25
22
 
26
- describe '#ack' do
27
- before do
28
- allow(ctx).to receive(:ack_message)
29
- end
30
- context 'when the adapter supports client acks' do
31
- before do
32
- allow(ctx).to receive(:supports_client_acks?) { true }
33
- end
34
- it 'calls #ack_message with the message' do
35
- subject.ack
36
- expect(ctx).to have_received(:ack_message).with(subject, {})
37
- end
38
- it 'passes the supplied options to ack_message' do
39
- subject.ack(options)
40
- expect(ctx).to have_received(:ack_message).with(subject, options)
41
- end
42
- end
43
- context "when the adapter doesn't support client acks" do
44
- before do
45
- allow(ctx).to receive(:supports_client_acks?) { false }
46
- end
47
- it "doesn't call #ack_message" do
48
- subject.ack
49
- expect(ctx).not_to have_received(:ack_message)
50
- end
51
- it 'logs a warning' do
52
- allow(logger).to receive(:debug)
53
- subject.ack
54
- expect(logger).to have_received(:debug).with('this adapter does not support client acks')
23
+ describe '#headers' do
24
+ it { expect(subject.headers).to eq(headers) }
25
+ end
26
+
27
+ describe '#properties' do
28
+ it { expect(subject.properties).to eq(properties) }
29
+ end
30
+
31
+ describe '#raw_body' do
32
+ it 'defaults to the body' do
33
+ expect(subject.raw_body).to eq(subject.body)
34
+ end
35
+
36
+ it 'can be provided in the constructor' do
37
+ msg = described_class.new(ctx, body, headers, properties, 'my_raw_body')
38
+
39
+ expect(msg.raw_body).to eq('my_raw_body')
40
+ expect(msg.body).to eq(body)
41
+ end
42
+ end
55
43
  end
56
44
  end
57
- end
58
45
 
59
- describe '#nack' do
60
- before do
61
- allow(ctx).to receive(:nack_message)
62
- end
63
- context 'when the adapter supports client nacks' do
46
+ let(:logger) { MessageDriver.logger }
47
+ let(:ctx) { double('adapter_context') }
48
+ let(:options) { double('options') }
49
+ subject(:message) { described_class.new(ctx, 'body', {}, {}) }
50
+
51
+ describe '#ack' do
64
52
  before do
65
- allow(ctx).to receive(:supports_client_acks?) { true }
53
+ allow(ctx).to receive(:ack_message)
66
54
  end
67
- it 'calls #nack_message with the message' do
68
- subject.nack
69
- expect(ctx).to have_received(:nack_message).with(subject, {})
55
+ context 'when the adapter supports client acks' do
56
+ before do
57
+ allow(ctx).to receive(:supports_client_acks?) { true }
58
+ end
59
+ it 'calls #ack_message with the message' do
60
+ subject.ack
61
+ expect(ctx).to have_received(:ack_message).with(subject, {})
62
+ end
63
+ it 'passes the supplied options to ack_message' do
64
+ subject.ack(options)
65
+ expect(ctx).to have_received(:ack_message).with(subject, options)
66
+ end
70
67
  end
71
- it 'passes the supplied options to nack_message' do
72
- subject.nack(options)
73
- expect(ctx).to have_received(:nack_message).with(subject, options)
68
+ context "when the adapter doesn't support client acks" do
69
+ before do
70
+ allow(ctx).to receive(:supports_client_acks?) { false }
71
+ end
72
+ it "doesn't call #ack_message" do
73
+ subject.ack
74
+ expect(ctx).not_to have_received(:ack_message)
75
+ end
76
+ it 'logs a warning' do
77
+ allow(logger).to receive(:debug)
78
+ subject.ack
79
+ expect(logger).to have_received(:debug).with('this adapter does not support client acks')
80
+ end
74
81
  end
75
82
  end
76
- context "when the adapter doesn't support client nacks" do
83
+
84
+ describe '#nack' do
77
85
  before do
78
- allow(ctx).to receive(:supports_client_acks?) { false }
86
+ allow(ctx).to receive(:nack_message)
79
87
  end
80
- it "doesn't call #nack_message" do
81
- subject.nack
82
- expect(ctx).not_to have_received(:nack_message)
88
+ context 'when the adapter supports client nacks' do
89
+ before do
90
+ allow(ctx).to receive(:supports_client_acks?) { true }
91
+ end
92
+ it 'calls #nack_message with the message' do
93
+ subject.nack
94
+ expect(ctx).to have_received(:nack_message).with(subject, {})
95
+ end
96
+ it 'passes the supplied options to nack_message' do
97
+ subject.nack(options)
98
+ expect(ctx).to have_received(:nack_message).with(subject, options)
99
+ end
83
100
  end
84
- it 'logs a warning' do
85
- allow(logger).to receive(:debug)
86
- subject.nack
87
- expect(logger).to have_received(:debug).with('this adapter does not support client acks')
101
+ context "when the adapter doesn't support client nacks" do
102
+ before do
103
+ allow(ctx).to receive(:supports_client_acks?) { false }
104
+ end
105
+ it "doesn't call #nack_message" do
106
+ subject.nack
107
+ expect(ctx).not_to have_received(:nack_message)
108
+ end
109
+ it 'logs a warning' do
110
+ allow(logger).to receive(:debug)
111
+ subject.nack
112
+ expect(logger).to have_received(:debug).with('this adapter does not support client acks')
113
+ end
88
114
  end
89
115
  end
90
116
  end
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+
3
+ module MessageDriver
4
+ module Middleware
5
+ RSpec.describe Base do
6
+ let(:destination) { double(Destination) }
7
+ subject(:middleware_base) { described_class.new(destination) }
8
+
9
+ let(:body) { double('body') }
10
+ let(:headers) { double('headers') }
11
+ let(:properties) { double('properties') }
12
+
13
+ describe '#destination' do
14
+ it { expect(subject.destination).to be destination }
15
+ end
16
+
17
+ describe '#on_publish' do
18
+ it 'just returns the input values' do
19
+ expect(subject.on_publish(body, headers, properties)).to eq [body, headers, properties]
20
+ end
21
+ end
22
+
23
+ describe '#on_consume' do
24
+ it 'just returns the input values' do
25
+ expect(subject.on_consume(body, headers, properties)).to eq [body, headers, properties]
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,82 @@
1
+ require 'spec_helper'
2
+
3
+ module MessageDriver
4
+ module Middleware
5
+ RSpec.describe BlockMiddleware do
6
+ let(:destination) { double(Destination) }
7
+ let(:a_block) { ->(b, h, p) { [b, h, p] } }
8
+ describe '#initialize' do
9
+ it 'requires you provide either an on_publish, or on_consume block' do
10
+ expect do
11
+ BlockMiddleware.new(destination, {})
12
+ end.to raise_error(ArgumentError)
13
+ expect do
14
+ BlockMiddleware.new(destination, foo: a_block)
15
+ end.to raise_error(ArgumentError)
16
+ expect do
17
+ BlockMiddleware.new(destination, on_publish: a_block)
18
+ end.not_to raise_error
19
+ expect do
20
+ BlockMiddleware.new(destination, on_consume: a_block)
21
+ end.not_to raise_error
22
+ expect do
23
+ BlockMiddleware.new(destination, on_consume: a_block, on_publish: a_block)
24
+ end.not_to raise_error
25
+ end
26
+
27
+ it 'saves the provided blocks' do
28
+ middleware = BlockMiddleware.new(destination, on_publish: a_block)
29
+ expect(middleware.on_publish_block).to be(a_block)
30
+ expect(middleware.on_consume_block).to be_nil
31
+
32
+ middleware = BlockMiddleware.new(destination, on_consume: a_block)
33
+ expect(middleware.on_publish_block).to be_nil
34
+ expect(middleware.on_consume_block).to be(a_block)
35
+
36
+ middleware = BlockMiddleware.new(destination, on_publish: a_block, on_consume: a_block)
37
+ expect(middleware.on_publish_block).to be(a_block)
38
+ expect(middleware.on_consume_block).to be(a_block)
39
+ end
40
+ end
41
+
42
+ shared_context 'a message processor' do |op|
43
+ let(:a_block) { double('a_block') }
44
+ let(:subject) { described_class.new(destination, op => a_block) }
45
+
46
+ let(:body) { double('body') }
47
+ let(:headers) { double('headers') }
48
+ let(:properties) { double('properties') }
49
+
50
+ let(:result_body) { double('result_body') }
51
+ let(:result_headers) { double('result_headers') }
52
+ let(:result_properties) { double('result_properties') }
53
+
54
+ before do
55
+ allow(a_block).to receive(:call).and_return([result_body, result_headers, result_properties]) unless a_block.nil?
56
+ end
57
+
58
+ it 'delegates to the provided block and returns it\'s result' do
59
+ result = subject.public_send(op, body, headers, properties)
60
+ expect(a_block).to have_received(:call).with(body, headers, properties)
61
+ expect(result).to eq([result_body, result_headers, result_properties])
62
+ end
63
+
64
+ context "when :#{op} was not provided" do
65
+ let(:a_block) { nil }
66
+ it 'just returns the original inputs' do
67
+ result = subject.public_send(op, body, headers, properties)
68
+ expect(result).to eq([body, headers, properties])
69
+ end
70
+ end
71
+ end
72
+
73
+ describe '#on_publish' do
74
+ it_behaves_like 'a message processor', :on_publish
75
+ end
76
+
77
+ describe '#on_consume' do
78
+ it_behaves_like 'a message processor', :on_consume
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,165 @@
1
+ require 'spec_helper'
2
+
3
+ module MessageDriver
4
+ module Middleware
5
+ RSpec.describe MiddlewareStack do
6
+ class Top < Base; end
7
+ class Middle < Base; end
8
+ class Bottom < Base; end
9
+
10
+ let(:top) { Top.new(destination) }
11
+ let(:middle) { Middle.new(destination) }
12
+ let(:bottom) { Bottom.new(destination) }
13
+
14
+ before do
15
+ allow(Top).to receive(:new).with(destination).and_return(top)
16
+ allow(Middle).to receive(:new).with(destination).and_return(middle)
17
+ allow(Bottom).to receive(:new).with(destination).and_return(bottom)
18
+ end
19
+
20
+ def load_middleware_doubles
21
+ subject.append Middle
22
+ subject.prepend Bottom
23
+ subject.append Top
24
+ end
25
+
26
+ let(:destination) { double(Destination) }
27
+ subject(:middleware_stack) { described_class.new(destination) }
28
+
29
+ it { is_expected.to be_an Enumerable }
30
+
31
+ describe '#destination' do
32
+ it { expect(subject.destination).to be destination }
33
+ end
34
+
35
+ describe '#middlewares' do
36
+ it 'is initially empty' do
37
+ expect(subject.middlewares).to be_an Array
38
+ expect(subject.middlewares).to be_empty
39
+ end
40
+
41
+ it 'returns the list of middlewares' do
42
+ load_middleware_doubles
43
+ expect(subject.middlewares).to eq [bottom, middle, top]
44
+ end
45
+
46
+ it 'ensures the returned list of middlewares is frozen' do
47
+ expect(subject.middlewares).to be_frozen
48
+ end
49
+ end
50
+
51
+ shared_examples 'a middleware builder' do |op|
52
+ it 'instantiates the middleware and passes the destination to it' do
53
+ allow(Top).to receive(:new).and_call_original
54
+ subject.public_send op, Top
55
+ middleware = subject.middlewares.first
56
+ expect(middleware).to be_an_instance_of Top
57
+ expect(middleware.destination).to be destination
58
+ end
59
+
60
+ it 'returns the instantiated middleware' do
61
+ expect(subject.public_send(op, Top)).to be top
62
+ end
63
+
64
+ context 'with a parameterizable middleware' do
65
+ class Paramed < Base
66
+ attr_reader :foo, :bar
67
+ def initialize(destination, foo, bar)
68
+ super(destination)
69
+ @foo = foo
70
+ @bar = bar
71
+ end
72
+ end
73
+
74
+ it 'passes the extra values to the middleware initializer' do
75
+ subject.public_send(op, Paramed, 27, 'a parameter value')
76
+ middleware = subject.middlewares.first
77
+ expect(middleware).to be_an_instance_of Paramed
78
+ expect(middleware.foo).to eq(27)
79
+ expect(middleware.bar).to eq('a parameter value')
80
+ end
81
+ end
82
+
83
+ context 'with a hash of blocks' do
84
+ let(:on_publish) { ->(b, h, p) { [b, h, p] } }
85
+ let(:on_consume) { ->(b, h, p) { [b, h, p] } }
86
+ before do
87
+ expect(on_publish).not_to eq(on_consume)
88
+ expect(on_publish).not_to be(on_consume)
89
+ end
90
+ it 'builds a BlockMiddleware with the provied on_publish and on_consume blocks' do
91
+ subject.public_send(op, on_publish: on_publish, on_consume: on_consume)
92
+ middleware = subject.middlewares.first
93
+ expect(middleware).to be_an_instance_of BlockMiddleware
94
+ expect(middleware.on_publish_block).to be on_publish
95
+ expect(middleware.on_consume_block).to be on_consume
96
+ end
97
+ end
98
+ end
99
+
100
+ describe '#append' do
101
+ it 'adds middlewares to the top of the middleware stack' do
102
+ subject.append Bottom
103
+ subject.append Middle
104
+ subject.append Top
105
+ expect(subject.middlewares).to eq [bottom, middle, top]
106
+ end
107
+
108
+ it_behaves_like 'a middleware builder', :append
109
+ end
110
+
111
+ describe '#prepend' do
112
+ it 'adds middlewares to the bottom of the middleware stack' do
113
+ subject.prepend Top
114
+ subject.prepend Middle
115
+ subject.prepend Bottom
116
+ expect(subject.middlewares).to eq [bottom, middle, top]
117
+ end
118
+
119
+ it_behaves_like 'a middleware builder', :prepend
120
+ end
121
+
122
+ describe '#on_publish' do
123
+ it 'passes the message data to each middleware\'s #on_publish message, bottom to top' do
124
+ load_middleware_doubles
125
+ expect(subject.middlewares).to eq [bottom, middle, top]
126
+
127
+ allow(bottom).to receive(:on_publish).and_call_original
128
+ allow(middle).to receive(:on_publish).and_call_original
129
+ allow(top).to receive(:on_publish).and_call_original
130
+
131
+ body = double('body')
132
+ headers = double('headers')
133
+ properties = double('properties')
134
+
135
+ expect(subject.on_publish(body, headers, properties)).to eq [body, headers, properties]
136
+
137
+ expect(bottom).to have_received(:on_publish).ordered
138
+ expect(middle).to have_received(:on_publish).ordered
139
+ expect(top).to have_received(:on_publish).ordered
140
+ end
141
+ end
142
+
143
+ describe '#on_consume' do
144
+ it 'passes the message data to each middleware\'s #on_consume message, top to bottom' do
145
+ load_middleware_doubles
146
+ expect(subject.middlewares).to eq [bottom, middle, top]
147
+
148
+ allow(bottom).to receive(:on_consume).and_call_original
149
+ allow(middle).to receive(:on_consume).and_call_original
150
+ allow(top).to receive(:on_consume).and_call_original
151
+
152
+ body = double('body')
153
+ headers = double('headers')
154
+ properties = double('properties')
155
+
156
+ expect(subject.on_consume(body, headers, properties)).to eq [body, headers, properties]
157
+
158
+ expect(top).to have_received(:on_consume).ordered
159
+ expect(middle).to have_received(:on_consume).ordered
160
+ expect(bottom).to have_received(:on_consume).ordered
161
+ end
162
+ end
163
+ end
164
+ end
165
+ end