message-driver 0.4.0 → 0.5.0

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