msgr 1.2.0 → 1.3.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 (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