msgr 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +8 -0
  3. data/.github/workflows/build.yml +52 -0
  4. data/.github/workflows/lint.yml +20 -0
  5. data/.rubocop.yml +9 -48
  6. data/.travis.yml +21 -35
  7. data/Appraisals +18 -0
  8. data/CHANGELOG.md +11 -1
  9. data/Gemfile +8 -15
  10. data/README.md +8 -20
  11. data/Rakefile +5 -5
  12. data/bin/msgr +1 -0
  13. data/gemfiles/rails_5.2.gemfile +14 -0
  14. data/gemfiles/rails_6.0.gemfile +14 -0
  15. data/gemfiles/rails_6.1.gemfile +14 -0
  16. data/gemfiles/rails_master.gemfile +14 -0
  17. data/lib/msgr.rb +1 -0
  18. data/lib/msgr/binding.rb +13 -8
  19. data/lib/msgr/channel.rb +5 -3
  20. data/lib/msgr/cli.rb +18 -11
  21. data/lib/msgr/client.rb +17 -20
  22. data/lib/msgr/connection.rb +13 -1
  23. data/lib/msgr/consumer.rb +2 -3
  24. data/lib/msgr/dispatcher.rb +7 -9
  25. data/lib/msgr/logging.rb +2 -0
  26. data/lib/msgr/message.rb +1 -2
  27. data/lib/msgr/railtie.rb +14 -69
  28. data/lib/msgr/route.rb +1 -4
  29. data/lib/msgr/routes.rb +2 -0
  30. data/lib/msgr/tasks/msgr/drain.rake +11 -0
  31. data/lib/msgr/test_pool.rb +1 -3
  32. data/lib/msgr/version.rb +1 -1
  33. data/msgr.gemspec +2 -6
  34. data/scripts/simple_test.rb +2 -3
  35. data/spec/fixtures/{msgr-routes-test-1.rb → msgr_routes_test_1.rb} +0 -0
  36. data/spec/integration/dummy/Rakefile +1 -1
  37. data/spec/{msgr/support/.keep → integration/dummy/app/assets/config/manifest.js} +0 -0
  38. data/spec/integration/dummy/bin/bundle +1 -1
  39. data/spec/integration/dummy/bin/rails +1 -1
  40. data/spec/integration/dummy/config/application.rb +1 -1
  41. data/spec/integration/dummy/config/boot.rb +2 -2
  42. data/spec/integration/dummy/config/environment.rb +1 -1
  43. data/spec/integration/dummy/config/rabbitmq.yml +1 -1
  44. data/spec/integration/msgr/dispatcher_spec.rb +28 -12
  45. data/spec/integration/msgr/railtie_spec.rb +10 -120
  46. data/spec/integration/spec_helper.rb +2 -3
  47. data/spec/integration/{msgr_spec.rb → test_controller_spec.rb} +1 -1
  48. data/spec/unit/msgr/client_spec.rb +88 -0
  49. data/spec/{msgr → unit}/msgr/connection_spec.rb +1 -1
  50. data/spec/{msgr → unit}/msgr/consumer_spec.rb +0 -0
  51. data/spec/unit/msgr/dispatcher_spec.rb +45 -0
  52. data/spec/{msgr → unit}/msgr/route_spec.rb +15 -14
  53. data/spec/{msgr → unit}/msgr/routes_spec.rb +32 -35
  54. data/spec/{msgr → unit}/msgr_spec.rb +25 -16
  55. data/spec/{msgr → unit}/spec_helper.rb +1 -1
  56. data/spec/unit/support/.keep +0 -0
  57. metadata +37 -33
  58. data/gemfiles/Gemfile.rails-4-2 +0 -7
  59. data/gemfiles/Gemfile.rails-5-0 +0 -7
  60. data/gemfiles/Gemfile.rails-5-1 +0 -7
  61. data/gemfiles/Gemfile.rails-5-2 +0 -7
  62. data/gemfiles/Gemfile.rails-master +0 -14
  63. data/spec/msgr/msgr/client_spec.rb +0 -60
  64. data/spec/msgr/msgr/dispatcher_spec.rb +0 -44
  65. data/spec/support/setup.rb +0 -29
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Msgr::Client do
6
+ subject(:client) { described_class.new config }
7
+
8
+ let(:config) { {} }
9
+
10
+ describe '#uri' do
11
+ subject(:uri) { client.uri.to_s }
12
+
13
+ context 'with default config' do
14
+ it 'uses the default config' do
15
+ expect(uri).to eq 'amqp://127.0.0.1'
16
+ end
17
+ end
18
+
19
+ context 'without vhost' do
20
+ let(:config) { {uri: 'amqp://rabbit'} }
21
+
22
+ it 'does not specify a vhost' do
23
+ expect(uri).to eq 'amqp://rabbit'
24
+ end
25
+ end
26
+
27
+ context 'with empty vhost' do
28
+ let(:config) { {uri: 'amqp://rabbit/'} }
29
+
30
+ it 'does not specify a vhost' do
31
+ expect(uri).to eq 'amqp://rabbit'
32
+ end
33
+ end
34
+
35
+ context 'with explicit vhost' do
36
+ let(:config) { {uri: 'amqp://rabbit/some_vhost'} }
37
+
38
+ # This behavior is due to legacy parsing in Msgr's config.
39
+ # We interpret the entire path (incl. the leading slash)
40
+ # as vhost. As per AMQP rules, this means the leading slash
41
+ # is part of the vhost, which means it has to be URL encoded.
42
+ # This will likely change with the next major release.
43
+ it 'uses the entire path as vhost' do
44
+ expect(uri).to eq 'amqp://rabbit/%2Fsome_vhost'
45
+ end
46
+ end
47
+
48
+ context 'with URI and vhost' do
49
+ let(:config) { {uri: 'amqp://rabbit/some_vhost', vhost: 'real_vhost'} }
50
+
51
+ # This is currently the only way to specify a vhost without
52
+ # leading slash (as a vhost in the :uri config would have
53
+ # an extra URL encoded leading slash).
54
+ it 'uses the explicit vhost' do
55
+ expect(uri).to eq 'amqp://rabbit/real_vhost'
56
+ end
57
+ end
58
+ end
59
+
60
+ describe 'drain' do
61
+ subject(:drain) { client.drain }
62
+
63
+ let(:channel_stub) { instance_double('Msgr::Channel', prefetch: true) }
64
+ let(:queue_stub) { instance_double('Bunny::Queue', purge: true) }
65
+
66
+ before do
67
+ client.routes.configure do
68
+ route 'abc', to: 'consumer1#action1'
69
+ route 'def', to: 'consumer1#action2'
70
+ route 'ghi', to: 'consumer2#action1'
71
+ end
72
+
73
+ allow(Msgr::Channel).to receive(:new).and_return(channel_stub)
74
+ allow(channel_stub).to receive(:queue).and_return(queue_stub).at_most(3).times
75
+ end
76
+
77
+ it 'requests purges for all configured routes' do
78
+ drain
79
+
80
+ expect(Msgr::Channel).to have_received(:new).exactly(3).times
81
+ expect(channel_stub).to have_received(:queue).with('msgr.consumer.Consumer1Consumer.action1', passive: true).once
82
+ expect(channel_stub).to have_received(:queue).with('msgr.consumer.Consumer1Consumer.action2', passive: true).once
83
+ expect(channel_stub).to have_received(:queue).with('msgr.consumer.Consumer2Consumer.action1', passive: true).once
84
+
85
+ expect(queue_stub).to have_received(:purge).exactly(3).times
86
+ end
87
+ end
88
+ end
@@ -6,7 +6,7 @@ describe Msgr::Connection do
6
6
  describe '#rebind' do
7
7
  let(:conn) { double }
8
8
  let(:routes) { Msgr::Routes.new }
9
- let(:connection) { Msgr::Connection.new conn, routes, dispatcher }
9
+ let(:connection) { described_class.new conn, routes, dispatcher }
10
10
 
11
11
  pending 'some tests missing -> only lets written'
12
12
  end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ class MsgrAutoAckConsumer < Msgr::Consumer
6
+ self.auto_ack = true
7
+
8
+ def index; end
9
+ end
10
+
11
+ class MsgrManualAckConsumer < Msgr::Consumer
12
+ self.auto_ack = false
13
+
14
+ def index; end
15
+ end
16
+
17
+ describe Msgr::Dispatcher do
18
+ subject { dispatcher }
19
+
20
+ let(:config) { {} }
21
+ let(:args) { [config] }
22
+ let(:dispatcher) { described_class.new(*args) }
23
+
24
+ describe 'dispatch' do
25
+ it 'acks messages automatically if auto_ack is enabled' do
26
+ route_db = instance_double('Msgr::Route', consumer: 'MsgrAutoAckConsumer', action: :index)
27
+ msg_db = instance_spy('Msgr::Message', route: route_db, acked?: false)
28
+
29
+ dispatcher.dispatch(msg_db)
30
+
31
+ expect(msg_db).to have_received(:ack)
32
+ expect(msg_db).not_to have_received(:nack)
33
+ end
34
+
35
+ it 'does not ack messages if auto_ack is disabled' do
36
+ route_db = instance_double('Msgr::Route', consumer: 'MsgrManualAckConsumer', action: :index)
37
+ msg_db = instance_spy('Msgr::Message', route: route_db, acked?: false)
38
+
39
+ dispatcher.dispatch(msg_db)
40
+
41
+ expect(msg_db).not_to have_received(:ack)
42
+ expect(msg_db).not_to have_received(:nack)
43
+ end
44
+ end
45
+ end
@@ -3,61 +3,62 @@
3
3
  require 'spec_helper'
4
4
 
5
5
  describe Msgr::Route do
6
+ subject { route }
7
+
6
8
  let(:routing_key) { 'routing.key.#' }
7
9
  let(:options) { {to: 'test#index'} }
8
10
  let(:args) { [routing_key, options] }
9
- let(:route) { Msgr::Route.new(*args) }
10
- subject { route }
11
+ let(:route) { described_class.new(*args) }
11
12
 
12
13
  describe '#initialize' do
13
- it 'should require `to` option' do
14
+ it 'requires `to` option' do
14
15
  expect do
15
- Msgr::Route.new(routing_key, {})
16
+ described_class.new(routing_key, {})
16
17
  end.to raise_error(ArgumentError)
17
18
  end
18
19
 
19
- it 'should require routing_key' do
20
+ it 'requires routing_key' do
20
21
  expect do
21
- Msgr::Route.new nil, options
22
+ described_class.new nil, options
22
23
  end.to raise_error(ArgumentError)
23
24
  end
24
25
 
25
- it 'should require not empty routing_key' do
26
+ it 'requires not empty routing_key' do
26
27
  expect do
27
- Msgr::Route.new '', options
28
+ described_class.new '', options
28
29
  end.to raise_error ArgumentError, /routing key required/i
29
30
  end
30
31
 
31
- it 'should require `to: "consumer#action` format' do
32
+ it 'requires `to: "consumer#action` format' do
32
33
  expect do
33
- Msgr::Route.new routing_key, to: 'abc'
34
+ described_class.new routing_key, to: 'abc'
34
35
  end.to raise_error ArgumentError, /invalid consumer format/i
35
36
  end
36
37
  end
37
38
 
38
39
  describe '#consumer' do
39
- it 'should return consumer class name' do
40
+ it 'returns consumer class name' do
40
41
  expect(route.consumer).to eq 'TestConsumer'
41
42
  end
42
43
 
43
44
  context 'with underscore consumer name' do
44
45
  let(:options) { super().merge to: 'test_resource_foo#index' }
45
46
 
46
- it 'should return camelized method name' do
47
+ it 'returns camelized method name' do
47
48
  expect(route.consumer).to eq 'TestResourceFooConsumer'
48
49
  end
49
50
  end
50
51
  end
51
52
 
52
53
  describe '#action' do
53
- it 'should return action method name' do
54
+ it 'returns action method name' do
54
55
  expect(route.action).to eq 'index'
55
56
  end
56
57
 
57
58
  context 'with camelCase action name' do
58
59
  let(:options) { super().merge to: 'test#myActionMethod' }
59
60
 
60
- it 'should return underscore method name' do
61
+ it 'returns underscore method name' do
61
62
  expect(route.action).to eq 'my_action_method'
62
63
  end
63
64
  end
@@ -3,25 +3,15 @@
3
3
  require 'spec_helper'
4
4
 
5
5
  describe Msgr::Routes do
6
- let(:routes) { Msgr::Routes.new }
6
+ let(:routes) { described_class.new }
7
7
 
8
8
  describe '#configure' do
9
- let(:block) { proc {} }
10
-
11
- it 'should evaluate given block within instance context' do
12
- expect(routes).to receive(:instance_eval) do |&p|
13
- expect(p).to be block
14
- end
15
-
16
- routes.configure(&block)
17
- end
18
-
19
- it 'should allow to call instance method in gven block' do
20
- expect(routes).to receive(:test_instance_method).with(:abc)
21
-
9
+ it 'allows to configure the instance in given block' do
22
10
  routes.configure do
23
- test_instance_method :abc
11
+ route 'abc', to: 'consumer#action'
24
12
  end
13
+
14
+ expect(routes.routes.first.keys).to eq ['abc']
25
15
  end
26
16
  end
27
17
 
@@ -35,7 +25,7 @@ describe Msgr::Routes do
35
25
 
36
26
  let(:each) { routes.each }
37
27
 
38
- it 'should iterate over configured routes' do
28
+ it 'iterates over configured routes' do
39
29
  expect(each.size).to eq 2
40
30
 
41
31
  expect(each.map(&:keys)).to eq [%w[abc.#], %w[edf.#]]
@@ -45,15 +35,16 @@ describe Msgr::Routes do
45
35
  end
46
36
 
47
37
  describe '#route' do
48
- let(:subject) { -> { routes.route 'routing.key', to: 'test2#index2' } }
38
+ subject(:route) { -> { routes.route 'routing.key', to: 'test2#index2' } }
39
+
49
40
  let(:last_route) { routes.routes.last }
50
41
 
51
- it 'should add a new route' do
52
- expect { subject.call }.to change { routes.routes.size }.from(0).to(1)
42
+ it 'adds a new route' do
43
+ expect { route.call }.to change { routes.routes.size }.from(0).to(1)
53
44
  end
54
45
 
55
- it 'should add given route' do
56
- subject.call
46
+ it 'adds given route' do
47
+ route.call
57
48
 
58
49
  expect(last_route.keys).to eq %w[routing.key]
59
50
  expect(last_route.consumer).to eq 'Test2Consumer'
@@ -61,26 +52,26 @@ describe Msgr::Routes do
61
52
  end
62
53
 
63
54
  context 'with same target' do
64
- let(:subject) do
55
+ subject(:route) do
65
56
  lambda do
66
57
  routes.route 'routing.key', to: 'test#index'
67
58
  routes.route 'another.routing.key', to: 'test#index'
68
59
  end
69
60
  end
70
61
 
71
- it 'should only add one new route' do
72
- expect { subject.call }.to change { routes.routes.size }.from(0).to(1)
62
+ it 'onlies add one new route' do
63
+ expect { route.call }.to change { routes.routes.size }.from(0).to(1)
73
64
  end
74
65
 
75
- it 'should add second binding to first route' do
76
- subject.call
66
+ it 'adds second binding to first route' do
67
+ route.call
77
68
  expect(routes.routes.first.keys).to eq %w[routing.key another.routing.key]
78
69
  end
79
70
  end
80
71
  end
81
72
 
82
73
  describe '#files' do
83
- it 'should allow to add route paths' do
74
+ it 'allows to add route paths' do
84
75
  routes.files << 'abc.rb'
85
76
  routes.files += %w[cde.rb edf.rb]
86
77
 
@@ -89,16 +80,20 @@ describe Msgr::Routes do
89
80
  end
90
81
 
91
82
  describe 'reload' do
92
- before { File.stub(:exist?).and_return(true) }
83
+ before do
84
+ allow(File).to receive(:exist?).and_return(true)
85
+ allow(routes).to receive(:load)
86
+ end
93
87
 
94
- it 'should trigger load for all files' do
95
- expect(routes).to receive(:load).with('cde.rb').ordered
96
- expect(routes).to receive(:load).with('edf.rb').ordered
88
+ it 'triggers load for all files' do
97
89
  routes.files += %w[cde.rb edf.rb]
98
90
  routes.reload
91
+
92
+ expect(routes).to have_received(:load).with('cde.rb').ordered
93
+ expect(routes).to have_received(:load).with('edf.rb').ordered
99
94
  end
100
95
 
101
- it 'should clear old routes before reloading' do
96
+ it 'clears old routes before reloading' do
102
97
  routes.route 'abc', to: 'abc#test'
103
98
  routes.reload
104
99
  expect(routes.each.size).to eq 0
@@ -106,11 +101,13 @@ describe Msgr::Routes do
106
101
  end
107
102
 
108
103
  describe 'load' do
109
- let(:file) { 'spec/fixtures/msgr-routes-test-1.rb' }
104
+ let(:file) { 'spec/fixtures/msgr_routes_test_1.rb' }
110
105
 
111
- it 'should eval given file within routes context' do
112
- expect(routes).to receive(:route).with('abc.#', to: 'test#index')
106
+ it 'evals given file within routes context' do
113
107
  routes.load file
108
+ expect(routes.routes.count).to eq 1
109
+ expect(routes.routes.first.keys).to eq ['abc.#']
110
+ expect(routes.routes.first.name).to eq 'msgr.consumer.TestConsumer.index'
114
111
  end
115
112
  end
116
113
  end
@@ -5,7 +5,6 @@ require 'spec_helper'
5
5
  class Receiver
6
6
  end
7
7
 
8
- #
9
8
  class MsgrTestConsumer < Msgr::Consumer
10
9
  def index
11
10
  Receiver.index
@@ -26,7 +25,7 @@ end
26
25
 
27
26
  describe Msgr do
28
27
  let(:queue) { Queue.new }
29
- let(:client) { Msgr::Client.new size: 1, prefix: SecureRandom.hex(2) }
28
+ let(:client) { Msgr::Client.new size: 1, prefix: SecureRandom.hex(2), uri: ENV['AMQP_SERVER'] }
30
29
 
31
30
  before do
32
31
  client.routes.configure do
@@ -36,43 +35,51 @@ describe Msgr do
36
35
  end
37
36
 
38
37
  client.start
38
+
39
+ allow(Receiver).to receive(:index) { queue << :end }
40
+ allow(Receiver).to receive(:batch) {|msg| queue << msg }
41
+
42
+ error_count = 0
43
+ allow(Receiver).to receive(:error) do
44
+ error_count += 1
45
+ raise 'error' if error_count == 1
46
+
47
+ queue << :end
48
+ end
39
49
  end
40
50
 
41
51
  after do
42
52
  client.stop delete: true
43
53
  end
44
54
 
45
- it 'should dispatch published methods to consumer' do
46
- expect(Receiver).to receive(:index) { queue << :end }
47
-
55
+ it 'dispatches published methods to consumer' do
48
56
  client.publish 'Payload', to: 'test.index'
49
57
 
50
58
  Timeout.timeout(4) { queue.pop }
51
- end
52
59
 
53
- it 'should redelivery failed messages' do
54
- expect(Receiver).to receive(:error).ordered.and_raise RuntimeError
55
- expect(Receiver).to receive(:error).ordered { queue << :end }
60
+ expect(Receiver).to have_received(:index).exactly(1).time
61
+ end
56
62
 
63
+ it 'redelivers failed messages' do
57
64
  client.publish 'Payload', to: 'test.error'
58
65
 
59
66
  Timeout.timeout(4) { queue.pop }
60
- end
61
67
 
62
- it 'should receive 2 messages when prefetch is set to 2' do
63
- expect(Receiver).to receive(:batch).twice { |msg| queue << msg }
68
+ expect(Receiver).to have_received(:error).exactly(2).times
69
+ end
64
70
 
71
+ it 'receives 2 messages when prefetch is set to 2' do
65
72
  2.times { client.publish 'Payload', to: 'test.batch' }
66
73
 
67
74
  2.times { Timeout.timeout(4) { queue.pop } }
68
- end
69
75
 
70
- it 'should not bulk ack all unacknowledged messages when acknowledging the last one' do
71
- expect(Receiver).to receive(:batch).exactly(3).times { |msg| queue << msg }
76
+ expect(Receiver).to have_received(:batch).exactly(2).times
77
+ end
72
78
 
79
+ it 'does not bulk ack all unacknowledged messages when acknowledging the last one' do
73
80
  2.times { client.publish 'Payload', to: 'test.batch' }
74
81
 
75
- messages = 2.times.map { Timeout.timeout(4) { queue.pop } }
82
+ messages = Array.new(2) { Timeout.timeout(4) { queue.pop } }
76
83
  messages[1].ack
77
84
  messages[0].nack
78
85
 
@@ -80,5 +87,7 @@ describe Msgr do
80
87
  message = Timeout.timeout(4) { queue.pop }
81
88
  expect(message.payload).to eq(messages[0].payload)
82
89
  expect(message.delivery_info.redelivered).to eq(true)
90
+
91
+ expect(Receiver).to have_received(:batch).exactly(3).times
83
92
  end
84
93
  end