msgr 1.2.0 → 1.4.0

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