msgr 1.2.0 → 1.4.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 (67) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +8 -0
  3. data/.github/workflows/test.yml +79 -0
  4. data/.rubocop.yml +12 -47
  5. data/Appraisals +23 -0
  6. data/CHANGELOG.md +87 -5
  7. data/Gemfile +9 -15
  8. data/README.md +11 -28
  9. data/Rakefile +10 -6
  10. data/bin/msgr +1 -0
  11. data/gemfiles/rails_5.2.gemfile +15 -0
  12. data/gemfiles/rails_6.0.gemfile +15 -0
  13. data/gemfiles/rails_6.1.gemfile +15 -0
  14. data/gemfiles/rails_7.0.gemfile +15 -0
  15. data/gemfiles/rails_head.gemfile +15 -0
  16. data/lib/msgr/binding.rb +13 -8
  17. data/lib/msgr/channel.rb +7 -10
  18. data/lib/msgr/cli.rb +26 -20
  19. data/lib/msgr/client.rb +27 -25
  20. data/lib/msgr/connection.rb +14 -2
  21. data/lib/msgr/consumer.rb +2 -3
  22. data/lib/msgr/dispatcher.rb +7 -9
  23. data/lib/msgr/logging.rb +2 -0
  24. data/lib/msgr/message.rb +1 -2
  25. data/lib/msgr/railtie.rb +14 -75
  26. data/lib/msgr/route.rb +2 -5
  27. data/lib/msgr/routes.rb +2 -0
  28. data/lib/msgr/tasks/msgr/drain.rake +11 -0
  29. data/lib/msgr/test_pool.rb +1 -3
  30. data/lib/msgr/version.rb +1 -1
  31. data/lib/msgr.rb +2 -3
  32. data/msgr.gemspec +8 -6
  33. data/renovate.json +5 -0
  34. data/scripts/simple_test.rb +3 -4
  35. data/spec/fixtures/{msgr-routes-test-1.rb → msgr_routes_test_1.rb} +0 -0
  36. data/spec/fixtures/msgr_routes_test_drain.rb +5 -0
  37. data/spec/integration/dummy/Rakefile +1 -1
  38. data/spec/{msgr/support/.keep → integration/dummy/app/assets/config/manifest.js} +0 -0
  39. data/spec/integration/dummy/bin/bundle +1 -1
  40. data/spec/integration/dummy/bin/rails +1 -1
  41. data/spec/integration/dummy/config/application.rb +1 -1
  42. data/spec/integration/dummy/config/boot.rb +2 -2
  43. data/spec/integration/dummy/config/environment.rb +1 -1
  44. data/spec/integration/dummy/config/rabbitmq.yml +1 -1
  45. data/spec/integration/msgr/dispatcher_spec.rb +28 -12
  46. data/spec/integration/msgr/railtie_spec.rb +10 -120
  47. data/spec/integration/spec_helper.rb +2 -3
  48. data/spec/integration/{msgr_spec.rb → test_controller_spec.rb} +1 -1
  49. data/spec/unit/msgr/client_spec.rb +83 -0
  50. data/spec/{msgr → unit}/msgr/connection_spec.rb +1 -1
  51. data/spec/{msgr → unit}/msgr/consumer_spec.rb +0 -0
  52. data/spec/unit/msgr/dispatcher_spec.rb +45 -0
  53. data/spec/{msgr → unit}/msgr/route_spec.rb +29 -14
  54. data/spec/{msgr → unit}/msgr/routes_spec.rb +32 -35
  55. data/spec/{msgr → unit}/msgr_spec.rb +26 -18
  56. data/spec/{msgr → unit}/spec_helper.rb +1 -1
  57. data/spec/unit/support/.keep +0 -0
  58. metadata +43 -36
  59. data/.travis.yml +0 -43
  60. data/gemfiles/Gemfile.rails-4-2 +0 -7
  61. data/gemfiles/Gemfile.rails-5-0 +0 -7
  62. data/gemfiles/Gemfile.rails-5-1 +0 -7
  63. data/gemfiles/Gemfile.rails-5-2 +0 -7
  64. data/gemfiles/Gemfile.rails-master +0 -14
  65. data/spec/msgr/msgr/client_spec.rb +0 -60
  66. data/spec/msgr/msgr/dispatcher_spec.rb +0 -44
  67. data/spec/support/setup.rb +0 -29
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ route 'abc', to: 'consumer1#action1'
4
+ route 'def', to: 'consumer1#action2'
5
+ route 'ghi', to: 'consumer2#action1'
@@ -3,6 +3,6 @@
3
3
  # Add your own tasks in files placed in lib/tasks ending in .rake,
4
4
  # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
5
5
 
6
- require File.expand_path('../config/application', __FILE__)
6
+ require File.expand_path('config/application', __dir__)
7
7
 
8
8
  Dummy::Application.load_tasks
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
4
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
5
5
  load Gem.bin_path('bundler', 'bundle')
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- APP_PATH = File.expand_path('../../config/application', __FILE__)
4
+ APP_PATH = File.expand_path('../config/application', __dir__)
5
5
  require_relative '../config/boot'
6
6
  require 'rails/commands'
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require File.expand_path('../boot', __FILE__)
3
+ require File.expand_path('boot', __dir__)
4
4
 
5
5
  require 'rails/all'
6
6
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Set up gems listed in the Gemfile.
4
- ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../../Gemfile', __FILE__)
4
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../Gemfile', __dir__)
5
5
 
6
6
  require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
7
- $LOAD_PATH.unshift File.expand_path('../../../../lib', __FILE__)
7
+ $LOAD_PATH.unshift File.expand_path('../../../lib', __dir__)
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Load the Rails application.
4
- require File.expand_path('../application', __FILE__)
4
+ require File.expand_path('application', __dir__)
5
5
 
6
6
  # Initialize the Rails application.
7
7
  Dummy::Application.initialize!
@@ -1,5 +1,5 @@
1
1
  common: &common
2
- uri: amqp://localhost/
2
+ uri: <%= ENV.fetch("AMQP_SERVER", "amqp://localhost/") %>
3
3
 
4
4
  test:
5
5
  <<: *common
@@ -4,7 +4,19 @@ require 'spec_helper'
4
4
 
5
5
  class DispatcherTestConsumer < Msgr::Consumer
6
6
  def index
7
- puts "<<< #{payload}"
7
+ self.class.called!
8
+ end
9
+
10
+ class << self
11
+ def calls
12
+ @calls ||= 0
13
+ end
14
+
15
+ attr_writer :calls
16
+
17
+ def called!
18
+ self.calls += 1
19
+ end
8
20
  end
9
21
  end
10
22
 
@@ -19,55 +31,59 @@ describe Msgr::Dispatcher do
19
31
  let(:config) { {max: 1} }
20
32
  let(:consumer) { 'DispatcherTestConsumer' }
21
33
  let(:route) do
22
- double(:route).tap do |t|
34
+ instance_double('Msgr::Route').tap do |t|
23
35
  allow(t).to receive(:consumer).and_return consumer
24
36
  allow(t).to receive(:action).and_return 'index'
25
37
  end
26
38
  end
27
39
  let(:channel) do
28
- double(:channel).tap do |c|
40
+ instance_double('Msgr::Channel').tap do |c|
29
41
  allow(c).to receive(:ack)
30
42
  end
31
43
  end
32
44
  let(:delivery_info) do
33
- double(:delivery_info).tap do |ti|
45
+ instance_double('Bunny::DeliveryInfo').tap do |ti|
34
46
  allow(ti).to receive(:delivery_tag).and_return(3)
35
47
  end
36
48
  end
37
49
  let(:payload) { {} }
38
50
  let(:metadata) do
39
- double(:metadata).tap do |metadata|
51
+ instance_double('Bunny::MessageProperties').tap do |metadata|
40
52
  allow(metadata).to receive(:content_type).and_return('text/plain')
41
53
  end
42
54
  end
43
55
  let(:message) { Msgr::Message.new channel, delivery_info, metadata, payload, route }
44
56
  let(:action) { -> { dispatcher.call message } }
45
57
 
46
- it 'should consume message' do
47
- expect_any_instance_of(DispatcherTestConsumer).to receive(:index)
48
- dispatcher.call message
58
+ it 'consumes message' do
59
+ expect do
60
+ dispatcher.call message
61
+ end.to change(DispatcherTestConsumer, :calls).by(1)
49
62
  end
50
63
 
51
64
  context 'with not acknowledged message' do
52
- before { dispatcher.call message }
53
65
  subject { message }
54
- it { should be_acked }
66
+
67
+ before { dispatcher.call message }
68
+
69
+ it { is_expected.to be_acked }
55
70
  end
56
71
 
57
72
  describe 'exception swallowing' do
58
73
  let(:consumer) { 'DispatcherRaiseConsumer' }
74
+
59
75
  before do
60
76
  allow(message).to receive(:nack)
61
77
  end
62
78
 
63
- it 'should swallow exceptions by default' do
79
+ it 'swallows exceptions by default' do
64
80
  expect { dispatcher.call(message) }.not_to raise_error
65
81
  end
66
82
 
67
83
  context 'with raise_exceptions configuration option and a synchronous pool' do
68
84
  let(:config) { super().merge(raise_exceptions: true) }
69
85
 
70
- it 'should raise the exception' do
86
+ it 'raises the exception' do
71
87
  expect { dispatcher.call(message) }.to raise_error(ArgumentError)
72
88
  end
73
89
  end
@@ -6,139 +6,29 @@ describe Msgr::Railtie do
6
6
  describe 'configuration options' do
7
7
  let(:config) { Rails.configuration }
8
8
 
9
- it 'should have `msgr` key' do
9
+ it 'has `msgr` key' do
10
10
  expect(config).to respond_to :msgr
11
11
  end
12
12
  end
13
13
 
14
- describe '#parse_config' do
15
- let(:settings) { {} }
16
- let(:action) { described_class.parse_config settings }
17
- subject { action }
18
-
19
- context 'with incorrect settings' do
20
- subject { -> { action } }
21
-
22
- context 'with config without url' do
23
- let(:settings) { {'test' => {hans: 'otto'}} }
24
-
25
- it { should raise_error 'Could not load rabbitmq environment config: URI missing.' }
26
- end
27
-
28
- context 'with invalid autostart value' do
29
- let(:settings) { {'test' => {uri: 'hans', autostart: 'unvalid'}} }
30
-
31
- it { should raise_error 'Invalid value for rabbitmq config autostart: "unvalid"' }
32
- end
33
-
34
- context 'with invalid checkcredentials value' do
35
- let(:settings) { {'test' => {uri: 'hans', checkcredentials: 'unvalid'}} }
36
-
37
- it { should raise_error 'Invalid value for rabbitmq config checkcredentials: "unvalid"' }
38
- end
39
-
40
- context 'with invalid raise_exceptions value' do
41
- let(:settings) { {'test' => {uri: 'franz', raise_exceptions: 'unvalid'}} }
42
-
43
- it { should raise_error 'Invalid value for rabbitmq config raise_exceptions: "unvalid"' }
44
- end
45
- end
46
-
47
- context 'without set routes file' do
48
- let(:settings) { {'test' => {uri: 'test'}} }
49
-
50
- context '[:routing_file]' do
51
- subject { super()[:routing_file] }
52
- it { should eq Rails.root.join('config/msgr.rb').to_s }
53
- end
54
- end
55
-
56
- context 'with set routes file' do
57
- let(:settings) { {'test' => {uri: 'test', 'routing_file' => 'my fancy file'}} }
58
-
59
- context '[:routing_file]' do
60
- subject { super()[:routing_file] }
61
- it { should eq 'my fancy file' }
62
- end
63
- end
64
-
65
- context 'with uri as symbol' do
66
- let(:settings) { {'test' => {uri: 'hans'}} }
67
-
68
- context '[:uri]' do
69
- subject { super()[:uri] }
70
- it { should eq 'hans' }
71
- end
72
- end
73
-
74
- context 'with uri as string' do
75
- let(:settings) { {'test' => {'uri' => 'hans'}} }
76
-
77
- context '[:uri]' do
78
- subject { super()[:uri] }
79
- it { should eq 'hans' }
80
- end
81
- end
82
-
83
- context 'without raise_exceptions config' do
84
- let(:settings) { {'test' => {'uri' => 'hans'}, 'development' => {'uri' => 'hans_dev'}} }
85
-
86
- describe '[:raise_exceptions]' do
87
- subject { super()[:raise_exceptions] }
88
- it { should eq false }
89
- end
90
- end
91
- end
92
-
93
14
  describe '#load' do
94
- let(:config) do
95
- cfg = ActiveSupport::OrderedOptions.new
96
- cfg.rabbitmq_config = Rails.root.join 'config', 'rabbitmq.yml'
97
- cfg
98
- end
99
-
100
- context 'with autostart is true' do
101
- it 'should not start Msgr' do
102
- expect(Msgr).to receive(:start)
103
- expect(Msgr::Railtie).to receive(:load_config).and_return('test' => {uri: 'test', autostart: true})
104
- Msgr::Railtie.load config
105
- end
106
- end
107
-
108
- context 'without autostart value' do
109
- it 'should not start Msgr' do
110
- expect(Msgr).to_not receive(:start)
111
- expect(Msgr::Railtie).to receive(:load_config).and_return('test' => {uri: 'test'})
112
- Msgr::Railtie.load config
113
- end
15
+ before do
16
+ allow(Msgr).to receive(:start)
17
+ allow(Msgr.client).to receive(:connect)
114
18
  end
115
19
 
116
20
  context 'without checkcredentials value' do
117
- it 'should connect to rabbitmq directly to check credentials' do
118
- expect_any_instance_of(Msgr::Client).to receive(:connect)
119
- expect(Msgr::Railtie).to receive(:load_config).and_return('test' => {uri: 'test'})
120
- Msgr::Railtie.load config
21
+ it 'connects to rabbitmq directly to check credentials' do
22
+ described_class.load({})
23
+ expect(Msgr.client).to have_received(:connect)
121
24
  end
122
25
  end
123
26
 
124
27
  context 'with checkcredentials is false' do
125
- it 'should connect to rabbitmq directly to check credentials' do
126
- expect_any_instance_of(Msgr::Client).to_not receive(:connect)
127
- expect(Msgr::Railtie).to receive(:load_config).and_return('test' => {uri: 'test', checkcredentials: false})
128
- Msgr::Railtie.load config
28
+ it 'connects to rabbitmq directly to check credentials' do
29
+ described_class.load({checkcredentials: false})
30
+ expect(Msgr.client).not_to have_received(:connect)
129
31
  end
130
32
  end
131
33
  end
132
-
133
- # describe '#load_config'
134
- # let(:options) { {} }
135
-
136
- # subject { Msgr::Railtie.load_config options }
137
-
138
- # if Rails::Application.methods.include(:config_for)
139
- # it 'should use config_for' do
140
-
141
- # end
142
- # end
143
- # end
144
34
  end
@@ -10,15 +10,14 @@ Coveralls.wear! do
10
10
  add_filter 'spec'
11
11
  end
12
12
 
13
- #
14
13
  ENV['RAILS_ENV'] ||= 'test'
15
14
  ENV['RAILS_GROUPS'] = ['rails', ENV['RAILS_GROUPS']].reject(&:nil?).join(',')
16
- require File.expand_path('../dummy/config/environment', __FILE__)
15
+ require File.expand_path('dummy/config/environment', __dir__)
17
16
  require 'rspec/rails'
18
17
 
19
18
  # Requires supporting ruby files with custom matchers and macros, etc,
20
19
  # in spec/support/ and its subdirectories.
21
- Dir[File.expand_path('../support/**/*.rb', __FILE__)].each {|f| require f }
20
+ Dir[File.expand_path('support/**/*.rb', __dir__)].sort.each {|f| require f }
22
21
 
23
22
  # Checks for pending migrations before tests are run.
24
23
  # If you are not using ActiveRecord, you can remove this line.
@@ -3,7 +3,7 @@
3
3
  require 'spec_helper'
4
4
 
5
5
  describe TestController, type: :request do
6
- it 'should send messages on :index' do
6
+ it 'sends messages on :index' do
7
7
  get '/'
8
8
  end
9
9
  end
@@ -0,0 +1,83 @@
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(:config) { {routing_file: 'spec/fixtures/msgr_routes_test_drain.rb'} }
64
+ let(:channel_stub) { instance_double('Msgr::Channel', prefetch: true) }
65
+ let(:queue_stub) { instance_double('Bunny::Queue', purge: true) }
66
+
67
+ before do
68
+ allow(Msgr::Channel).to receive(:new).and_return(channel_stub)
69
+ allow(channel_stub).to receive(:queue).and_return(queue_stub).at_most(3).times
70
+ end
71
+
72
+ it 'requests purges for all configured routes' do
73
+ drain
74
+
75
+ expect(Msgr::Channel).to have_received(:new).exactly(3).times
76
+ expect(channel_stub).to have_received(:queue).with('msgr.consumer.Consumer1Consumer.action1', passive: true).once
77
+ expect(channel_stub).to have_received(:queue).with('msgr.consumer.Consumer1Consumer.action2', passive: true).once
78
+ expect(channel_stub).to have_received(:queue).with('msgr.consumer.Consumer2Consumer.action1', passive: true).once
79
+
80
+ expect(queue_stub).to have_received(:purge).exactly(3).times
81
+ end
82
+ end
83
+ 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
File without changes
@@ -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,76 @@
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
37
+
38
+ it 'allows namespaces in consumer' do
39
+ expect do
40
+ described_class.new routing_key, to: 'abc/def#ghi'
41
+ end.not_to raise_error
42
+ end
36
43
  end
37
44
 
38
45
  describe '#consumer' do
39
- it 'should return consumer class name' do
46
+ it 'returns consumer class name' do
40
47
  expect(route.consumer).to eq 'TestConsumer'
41
48
  end
42
49
 
43
50
  context 'with underscore consumer name' do
44
51
  let(:options) { super().merge to: 'test_resource_foo#index' }
45
52
 
46
- it 'should return camelized method name' do
53
+ it 'returns camelized class name' do
47
54
  expect(route.consumer).to eq 'TestResourceFooConsumer'
48
55
  end
49
56
  end
57
+
58
+ context 'with nested namespace in consumer name' do
59
+ let(:options) { super().merge to: 'nested/namespace/foo#index' }
60
+
61
+ it 'returns fully classified, classified class name' do
62
+ expect(route.consumer).to eq 'Nested::Namespace::FooConsumer'
63
+ end
64
+ end
50
65
  end
51
66
 
52
67
  describe '#action' do
53
- it 'should return action method name' do
68
+ it 'returns action method name' do
54
69
  expect(route.action).to eq 'index'
55
70
  end
56
71
 
57
72
  context 'with camelCase action name' do
58
73
  let(:options) { super().merge to: 'test#myActionMethod' }
59
74
 
60
- it 'should return underscore method name' do
75
+ it 'returns underscore method name' do
61
76
  expect(route.action).to eq 'my_action_method'
62
77
  end
63
78
  end