grape 0.13.0 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of grape might be problematic. Click here for more details.

Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/Appraisals +9 -4
  3. data/CHANGELOG.md +28 -0
  4. data/Gemfile +0 -1
  5. data/Gemfile.lock +166 -0
  6. data/README.md +305 -163
  7. data/Rakefile +30 -33
  8. data/UPGRADING.md +31 -0
  9. data/benchmark/simple.rb +27 -0
  10. data/gemfiles/rack_1.5.2.gemfile +13 -0
  11. data/gemfiles/rails_3.gemfile +2 -2
  12. data/gemfiles/rails_4.gemfile +1 -2
  13. data/grape.gemspec +5 -4
  14. data/lib/grape.rb +9 -5
  15. data/lib/grape/dsl/configuration.rb +5 -2
  16. data/lib/grape/dsl/helpers.rb +8 -3
  17. data/lib/grape/dsl/inside_route.rb +67 -44
  18. data/lib/grape/dsl/parameters.rb +21 -12
  19. data/lib/grape/dsl/request_response.rb +1 -1
  20. data/lib/grape/dsl/routing.rb +3 -4
  21. data/lib/grape/endpoint.rb +63 -28
  22. data/lib/grape/error_formatter/base.rb +6 -6
  23. data/lib/grape/exceptions/base.rb +5 -5
  24. data/lib/grape/exceptions/invalid_version_header.rb +10 -0
  25. data/lib/grape/formatter/serializable_hash.rb +3 -2
  26. data/lib/grape/locale/en.yml +4 -1
  27. data/lib/grape/middleware/auth/base.rb +2 -2
  28. data/lib/grape/middleware/auth/dsl.rb +1 -1
  29. data/lib/grape/middleware/auth/strategies.rb +1 -1
  30. data/lib/grape/middleware/base.rb +7 -4
  31. data/lib/grape/middleware/error.rb +3 -2
  32. data/lib/grape/middleware/filter.rb +1 -1
  33. data/lib/grape/middleware/formatter.rb +47 -44
  34. data/lib/grape/middleware/globals.rb +3 -3
  35. data/lib/grape/middleware/versioner/accept_version_header.rb +5 -7
  36. data/lib/grape/middleware/versioner/header.rb +113 -50
  37. data/lib/grape/middleware/versioner/param.rb +5 -8
  38. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +20 -0
  39. data/lib/grape/middleware/versioner/path.rb +3 -6
  40. data/lib/grape/path.rb +3 -3
  41. data/lib/grape/request.rb +40 -0
  42. data/lib/grape/util/content_types.rb +9 -9
  43. data/lib/grape/util/env.rb +22 -0
  44. data/lib/grape/util/strict_hash_configuration.rb +2 -1
  45. data/lib/grape/validations/attributes_iterator.rb +8 -3
  46. data/lib/grape/validations/params_scope.rb +83 -15
  47. data/lib/grape/validations/types.rb +144 -0
  48. data/lib/grape/validations/types/build_coercer.rb +53 -0
  49. data/lib/grape/validations/types/custom_type_coercer.rb +183 -0
  50. data/lib/grape/validations/types/file.rb +28 -0
  51. data/lib/grape/validations/types/json.rb +65 -0
  52. data/lib/grape/validations/types/multiple_type_coercer.rb +76 -0
  53. data/lib/grape/validations/types/variant_collection_coercer.rb +59 -0
  54. data/lib/grape/validations/types/virtus_collection_patch.rb +16 -0
  55. data/lib/grape/validations/validators/all_or_none.rb +1 -1
  56. data/lib/grape/validations/validators/allow_blank.rb +3 -3
  57. data/lib/grape/validations/validators/base.rb +7 -0
  58. data/lib/grape/validations/validators/coerce.rb +31 -42
  59. data/lib/grape/validations/validators/presence.rb +2 -3
  60. data/lib/grape/validations/validators/regexp.rb +2 -4
  61. data/lib/grape/validations/validators/values.rb +3 -3
  62. data/lib/grape/version.rb +1 -1
  63. data/pkg/grape-0.13.0.gem +0 -0
  64. data/spec/grape/api/custom_validations_spec.rb +5 -4
  65. data/spec/grape/api/deeply_included_options_spec.rb +7 -7
  66. data/spec/grape/api/nested_helpers_spec.rb +4 -2
  67. data/spec/grape/api/shared_helpers_spec.rb +8 -8
  68. data/spec/grape/api_spec.rb +88 -54
  69. data/spec/grape/dsl/configuration_spec.rb +13 -0
  70. data/spec/grape/dsl/helpers_spec.rb +16 -2
  71. data/spec/grape/dsl/inside_route_spec.rb +3 -2
  72. data/spec/grape/dsl/parameters_spec.rb +0 -6
  73. data/spec/grape/dsl/routing_spec.rb +1 -1
  74. data/spec/grape/endpoint_spec.rb +61 -20
  75. data/spec/grape/entity_spec.rb +10 -8
  76. data/spec/grape/exceptions/invalid_accept_header_spec.rb +1 -15
  77. data/spec/grape/integration/rack_spec.rb +3 -2
  78. data/spec/grape/middleware/base_spec.rb +7 -5
  79. data/spec/grape/middleware/error_spec.rb +16 -15
  80. data/spec/grape/middleware/exception_spec.rb +45 -43
  81. data/spec/grape/middleware/formatter_spec.rb +34 -0
  82. data/spec/grape/middleware/versioner/header_spec.rb +79 -47
  83. data/spec/grape/path_spec.rb +10 -10
  84. data/spec/grape/presenters/presenter_spec.rb +2 -2
  85. data/spec/grape/request_spec.rb +100 -0
  86. data/spec/grape/validations/params_scope_spec.rb +11 -9
  87. data/spec/grape/validations/types_spec.rb +95 -0
  88. data/spec/grape/validations/validators/coerce_spec.rb +335 -2
  89. data/spec/grape/validations/validators/values_spec.rb +15 -15
  90. data/spec/grape/validations_spec.rb +53 -24
  91. data/spec/shared/versioning_examples.rb +2 -2
  92. data/spec/spec_helper.rb +0 -1
  93. data/spec/support/versioned_helpers.rb +2 -2
  94. metadata +51 -13
  95. data/.gitignore +0 -46
  96. data/.rspec +0 -2
  97. data/.rubocop.yml +0 -7
  98. data/.rubocop_todo.yml +0 -84
  99. data/.travis.yml +0 -20
  100. data/.yardopts +0 -2
  101. data/lib/grape/http/request.rb +0 -35
  102. data/lib/grape/util/parameter_types.rb +0 -58
  103. data/spec/grape/util/parameter_types_spec.rb +0 -54
@@ -3,48 +3,50 @@ require 'active_support/core_ext/hash'
3
3
 
4
4
  describe Grape::Middleware::Error do
5
5
  # raises a text exception
6
- class ExceptionApp
7
- class << self
8
- def call(_env)
9
- fail 'rain!'
6
+ module ExceptionSpec
7
+ class ExceptionApp
8
+ class << self
9
+ def call(_env)
10
+ fail 'rain!'
11
+ end
10
12
  end
11
13
  end
12
- end
13
14
 
14
- # raises a hash error
15
- class ErrorHashApp
16
- class << self
17
- def error!(message, status)
18
- throw :error, message: { error: message, detail: 'missing widget' }, status: status
19
- end
15
+ # raises a hash error
16
+ class ErrorHashApp
17
+ class << self
18
+ def error!(message, status)
19
+ throw :error, message: { error: message, detail: 'missing widget' }, status: status
20
+ end
20
21
 
21
- def call(_env)
22
- error!('rain!', 401)
22
+ def call(_env)
23
+ error!('rain!', 401)
24
+ end
23
25
  end
24
26
  end
25
- end
26
27
 
27
- # raises an error!
28
- class AccessDeniedApp
29
- class << self
30
- def error!(message, status)
31
- throw :error, message: message, status: status
32
- end
28
+ # raises an error!
29
+ class AccessDeniedApp
30
+ class << self
31
+ def error!(message, status)
32
+ throw :error, message: message, status: status
33
+ end
33
34
 
34
- def call(_env)
35
- error!('Access Denied', 401)
35
+ def call(_env)
36
+ error!('Access Denied', 401)
37
+ end
36
38
  end
37
39
  end
38
- end
39
40
 
40
- # raises a custom error
41
- class CustomError < Grape::Exceptions::Base
42
- end
41
+ # raises a custom error
42
+ class CustomError < Grape::Exceptions::Base
43
+ end
43
44
 
44
- class CustomErrorApp
45
- class << self
46
- def call(_env)
47
- fail CustomError, status: 400, message: 'failed validation'
45
+ class CustomErrorApp
46
+ class << self
47
+ def call(_env)
48
+ fail CustomError, status: 400, message: 'failed validation'
49
+ end
48
50
  end
49
51
  end
50
52
  end
@@ -55,7 +57,7 @@ describe Grape::Middleware::Error do
55
57
  @app ||= Rack::Builder.app do
56
58
  use Spec::Support::EndpointFaker
57
59
  use Grape::Middleware::Error
58
- run ExceptionApp
60
+ run ExceptionSpec::ExceptionApp
59
61
  end
60
62
  expect { get '/' }.to raise_error
61
63
  end
@@ -65,7 +67,7 @@ describe Grape::Middleware::Error do
65
67
  @app ||= Rack::Builder.app do
66
68
  use Spec::Support::EndpointFaker
67
69
  use Grape::Middleware::Error, rescue_all: true
68
- run ExceptionApp
70
+ run ExceptionSpec::ExceptionApp
69
71
  end
70
72
  get '/'
71
73
  expect(last_response.body).to eq('rain!')
@@ -75,7 +77,7 @@ describe Grape::Middleware::Error do
75
77
  @app ||= Rack::Builder.app do
76
78
  use Spec::Support::EndpointFaker
77
79
  use Grape::Middleware::Error, rescue_all: true
78
- run ExceptionApp
80
+ run ExceptionSpec::ExceptionApp
79
81
  end
80
82
  get '/'
81
83
  expect(last_response.status).to eq(500)
@@ -85,7 +87,7 @@ describe Grape::Middleware::Error do
85
87
  @app ||= Rack::Builder.app do
86
88
  use Spec::Support::EndpointFaker
87
89
  use Grape::Middleware::Error, rescue_all: true, default_status: 500
88
- run ExceptionApp
90
+ run ExceptionSpec::ExceptionApp
89
91
  end
90
92
  get '/'
91
93
  expect(last_response.status).to eq(500)
@@ -95,7 +97,7 @@ describe Grape::Middleware::Error do
95
97
  @app ||= Rack::Builder.app do
96
98
  use Spec::Support::EndpointFaker
97
99
  use Grape::Middleware::Error, rescue_all: true, format: :json
98
- run ExceptionApp
100
+ run ExceptionSpec::ExceptionApp
99
101
  end
100
102
  get '/'
101
103
  expect(last_response.body).to eq('{"error":"rain!"}')
@@ -105,7 +107,7 @@ describe Grape::Middleware::Error do
105
107
  @app ||= Rack::Builder.app do
106
108
  use Spec::Support::EndpointFaker
107
109
  use Grape::Middleware::Error, rescue_all: true, format: :json
108
- run ErrorHashApp
110
+ run ExceptionSpec::ErrorHashApp
109
111
  end
110
112
  get '/'
111
113
  expect(['{"error":"rain!","detail":"missing widget"}',
@@ -116,7 +118,7 @@ describe Grape::Middleware::Error do
116
118
  @app ||= Rack::Builder.app do
117
119
  use Spec::Support::EndpointFaker
118
120
  use Grape::Middleware::Error, rescue_all: true, format: :jsonapi
119
- run ExceptionApp
121
+ run ExceptionSpec::ExceptionApp
120
122
  end
121
123
  get '/'
122
124
  expect(last_response.body).to eq('{"error":"rain!"}')
@@ -126,7 +128,7 @@ describe Grape::Middleware::Error do
126
128
  @app ||= Rack::Builder.app do
127
129
  use Spec::Support::EndpointFaker
128
130
  use Grape::Middleware::Error, rescue_all: true, format: :jsonapi
129
- run ErrorHashApp
131
+ run ExceptionSpec::ErrorHashApp
130
132
  end
131
133
  get '/'
132
134
  expect(['{"error":"rain!","detail":"missing widget"}',
@@ -137,7 +139,7 @@ describe Grape::Middleware::Error do
137
139
  @app ||= Rack::Builder.app do
138
140
  use Spec::Support::EndpointFaker
139
141
  use Grape::Middleware::Error, rescue_all: true, format: :xml
140
- run ExceptionApp
142
+ run ExceptionSpec::ExceptionApp
141
143
  end
142
144
  get '/'
143
145
  expect(last_response.body).to eq("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<error>\n <message>rain!</message>\n</error>\n")
@@ -147,7 +149,7 @@ describe Grape::Middleware::Error do
147
149
  @app ||= Rack::Builder.app do
148
150
  use Spec::Support::EndpointFaker
149
151
  use Grape::Middleware::Error, rescue_all: true, format: :xml
150
- run ErrorHashApp
152
+ run ExceptionSpec::ErrorHashApp
151
153
  end
152
154
  get '/'
153
155
  expect(["<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<error>\n <detail>missing widget</detail>\n <error>rain!</error>\n</error>\n",
@@ -164,7 +166,7 @@ describe Grape::Middleware::Error do
164
166
  { custom_formatter: message }.inspect
165
167
  end
166
168
  }
167
- run ExceptionApp
169
+ run ExceptionSpec::ExceptionApp
168
170
  end
169
171
  get '/'
170
172
  expect(last_response.body).to eq('{:custom_formatter=>"rain!"}')
@@ -174,7 +176,7 @@ describe Grape::Middleware::Error do
174
176
  @app ||= Rack::Builder.app do
175
177
  use Spec::Support::EndpointFaker
176
178
  use Grape::Middleware::Error
177
- run AccessDeniedApp
179
+ run ExceptionSpec::AccessDeniedApp
178
180
  end
179
181
  get '/'
180
182
  expect(last_response.status).to eq(401)
@@ -184,7 +186,7 @@ describe Grape::Middleware::Error do
184
186
  @app ||= Rack::Builder.app do
185
187
  use Spec::Support::EndpointFaker
186
188
  use Grape::Middleware::Error, rescue_all: false
187
- run CustomErrorApp
189
+ run ExceptionSpec::CustomErrorApp
188
190
  end
189
191
 
190
192
  get '/'
@@ -132,6 +132,18 @@ describe Grape::Middleware::Formatter do
132
132
  expect(subject.env['api.format']).to eq(:xml)
133
133
  end
134
134
 
135
+ context 'with custom vendored content types' do
136
+ before do
137
+ subject.options[:content_types] = {}
138
+ subject.options[:content_types][:custom] = 'application/vnd.test+json'
139
+ end
140
+
141
+ it 'it uses the custom type' do
142
+ subject.call('PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/vnd.test+json')
143
+ expect(subject.env['api.format']).to eq(:custom)
144
+ end
145
+ end
146
+
135
147
  it 'parses headers with symbols as hash keys' do
136
148
  subject.call('PATH_INFO' => '/info', 'http_accept' => 'application/xml', system_time: '091293')
137
149
  expect(subject.env[:system_time]).to eq('091293')
@@ -157,6 +169,16 @@ describe Grape::Middleware::Formatter do
157
169
  _, headers, = subject.call('PATH_INFO' => '/info.custom')
158
170
  expect(headers['Content-type']).to eq('application/x-custom')
159
171
  end
172
+ it 'is set for vendored with registered type' do
173
+ subject.options[:content_types] = {}
174
+ subject.options[:content_types][:custom] = 'application/vnd.test+json'
175
+ _, headers, = subject.call('PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/vnd.test+json')
176
+ expect(headers['Content-type']).to eq('application/vnd.test+json')
177
+ end
178
+ it 'is set to closest generic for custom vendored/versioned without registered type' do
179
+ _, headers, = subject.call('PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/vnd.test+json')
180
+ expect(headers['Content-type']).to eq('application/json')
181
+ end
160
182
  end
161
183
 
162
184
  context 'format' do
@@ -179,6 +201,18 @@ describe Grape::Middleware::Formatter do
179
201
  end
180
202
  end
181
203
 
204
+ context 'no content responses' do
205
+ let(:no_content_response) { ->(status) { [status, {}, ['']] } }
206
+
207
+ Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.each do |status|
208
+ it "does not modify a #{status} response" do
209
+ expected_response = no_content_response[status]
210
+ allow(app).to receive(:call).and_return(expected_response)
211
+ expect(subject.call({})).to eq(expected_response)
212
+ end
213
+ end
214
+ end
215
+
182
216
  context 'input' do
183
217
  %w(POST PATCH PUT DELETE).each do |method|
184
218
  ['application/json', 'application/json; charset=utf-8'].each do |content_type|
@@ -56,13 +56,13 @@ describe Grape::Middleware::Versioner::Header do
56
56
  end
57
57
 
58
58
  it 'is set' do
59
- status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1+json')
59
+ status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1+json')
60
60
  expect(env['api.format']).to eql 'json'
61
61
  expect(status).to eq(200)
62
62
  end
63
63
 
64
64
  it 'is nil if not provided' do
65
- status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1')
65
+ status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1')
66
66
  expect(env['api.format']).to eql nil
67
67
  expect(status).to eq(200)
68
68
  end
@@ -72,13 +72,13 @@ describe Grape::Middleware::Versioner::Header do
72
72
 
73
73
  context 'api.vendor' do
74
74
  it 'is set' do
75
- status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor')
75
+ status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor')
76
76
  expect(env['api.vendor']).to eql 'vendor'
77
77
  expect(status).to eq(200)
78
78
  end
79
79
 
80
80
  it 'is set if format provided' do
81
- status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor+json')
81
+ status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor+json')
82
82
  expect(env['api.vendor']).to eql 'vendor'
83
83
  expect(status).to eq(200)
84
84
  end
@@ -89,7 +89,7 @@ describe Grape::Middleware::Versioner::Header do
89
89
  expect(exception).to be_a(Grape::Exceptions::InvalidAcceptHeader)
90
90
  expect(exception.headers).to eql('X-Cascade' => 'pass')
91
91
  expect(exception.status).to eql 406
92
- expect(exception.message).to include 'API vendor or version not found'
92
+ expect(exception.message).to include 'API vendor not found'
93
93
  end
94
94
  end
95
95
 
@@ -99,13 +99,13 @@ describe Grape::Middleware::Versioner::Header do
99
99
  end
100
100
 
101
101
  it 'is set' do
102
- status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1')
102
+ status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1')
103
103
  expect(env['api.vendor']).to eql 'vendor'
104
104
  expect(status).to eq(200)
105
105
  end
106
106
 
107
107
  it 'is set if format provided' do
108
- status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1+json')
108
+ status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1+json')
109
109
  expect(env['api.vendor']).to eql 'vendor'
110
110
  expect(status).to eq(200)
111
111
  end
@@ -116,7 +116,7 @@ describe Grape::Middleware::Versioner::Header do
116
116
  expect(exception).to be_a(Grape::Exceptions::InvalidAcceptHeader)
117
117
  expect(exception.headers).to eql('X-Cascade' => 'pass')
118
118
  expect(exception.status).to eql 406
119
- expect(exception.message).to include('API vendor or version not found')
119
+ expect(exception.message).to include('API vendor not found')
120
120
  end
121
121
  end
122
122
  end
@@ -128,23 +128,23 @@ describe Grape::Middleware::Versioner::Header do
128
128
  end
129
129
 
130
130
  it 'is set' do
131
- status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1')
131
+ status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1')
132
132
  expect(env['api.version']).to eql 'v1'
133
133
  expect(status).to eq(200)
134
134
  end
135
135
 
136
136
  it 'is set if format provided' do
137
- status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1+json')
137
+ status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1+json')
138
138
  expect(env['api.version']).to eql 'v1'
139
139
  expect(status).to eq(200)
140
140
  end
141
141
 
142
142
  it 'fails with 406 Not Acceptable if version is invalid' do
143
143
  expect { subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v2+json').last }.to raise_exception do |exception|
144
- expect(exception).to be_a(Grape::Exceptions::InvalidAcceptHeader)
144
+ expect(exception).to be_a(Grape::Exceptions::InvalidVersionHeader)
145
145
  expect(exception.headers).to eql('X-Cascade' => 'pass')
146
146
  expect(exception.status).to eql 406
147
- expect(exception.message).to include('API vendor or version not found')
147
+ expect(exception.message).to include('API version not found')
148
148
  end
149
149
  end
150
150
  end
@@ -184,24 +184,6 @@ describe Grape::Middleware::Versioner::Header do
184
184
  end
185
185
  end
186
186
 
187
- it 'fails with 406 Not Acceptable if type is a range' do
188
- expect { subject.call('HTTP_ACCEPT' => '*/*').last }.to raise_exception do |exception|
189
- expect(exception).to be_a(Grape::Exceptions::InvalidAcceptHeader)
190
- expect(exception.headers).to eql('X-Cascade' => 'pass')
191
- expect(exception.status).to eql 406
192
- expect(exception.message).to include('Accept header must not contain ranges ("*").')
193
- end
194
- end
195
-
196
- it 'fails with 406 Not Acceptable if subtype is a range' do
197
- expect { subject.call('HTTP_ACCEPT' => 'application/*').last }.to raise_exception do |exception|
198
- expect(exception).to be_a(Grape::Exceptions::InvalidAcceptHeader)
199
- expect(exception.headers).to eql('X-Cascade' => 'pass')
200
- expect(exception.status).to eql 406
201
- expect(exception.message).to include('Accept header must not contain ranges ("*").')
202
- end
203
- end
204
-
205
187
  it 'succeeds if proper header is set' do
206
188
  expect(subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1+json').first).to eq(200)
207
189
  end
@@ -223,30 +205,22 @@ describe Grape::Middleware::Versioner::Header do
223
205
  end
224
206
  end
225
207
 
226
- it 'fails with 406 Not Acceptable if header is empty' do
227
- expect { subject.call('HTTP_ACCEPT' => '').last }.to raise_exception do |exception|
228
- expect(exception).to be_a(Grape::Exceptions::InvalidAcceptHeader)
229
- expect(exception.headers).to eql({})
230
- expect(exception.status).to eql 406
231
- expect(exception.message).to include('Accept header must be set.')
232
- end
233
- end
234
-
235
- it 'fails with 406 Not Acceptable if type is a range' do
236
- expect { subject.call('HTTP_ACCEPT' => '*/*').last }.to raise_exception do |exception|
208
+ it 'fails with 406 Not Acceptable if header is application/xml' do
209
+ expect { subject.call('HTTP_ACCEPT' => 'application/xml').last }
210
+ .to raise_exception do |exception|
237
211
  expect(exception).to be_a(Grape::Exceptions::InvalidAcceptHeader)
238
212
  expect(exception.headers).to eql({})
239
213
  expect(exception.status).to eql 406
240
- expect(exception.message).to include('Accept header must not contain ranges ("*").')
214
+ expect(exception.message).to include('API vendor or version not found.')
241
215
  end
242
216
  end
243
217
 
244
- it 'fails with 406 Not Acceptable if subtype is a range' do
245
- expect { subject.call('HTTP_ACCEPT' => 'application/*').last }.to raise_exception do |exception|
218
+ it 'fails with 406 Not Acceptable if header is empty' do
219
+ expect { subject.call('HTTP_ACCEPT' => '').last }.to raise_exception do |exception|
246
220
  expect(exception).to be_a(Grape::Exceptions::InvalidAcceptHeader)
247
221
  expect(exception.headers).to eql({})
248
222
  expect(exception.status).to eql 406
249
- expect(exception.message).to include('Accept header must not contain ranges ("*").')
223
+ expect(exception.message).to include('Accept header must be set.')
250
224
  end
251
225
  end
252
226
 
@@ -270,10 +244,68 @@ describe Grape::Middleware::Versioner::Header do
270
244
 
271
245
  it 'fails with another version' do
272
246
  expect { subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v3+json') }.to raise_exception do |exception|
273
- expect(exception).to be_a(Grape::Exceptions::InvalidAcceptHeader)
247
+ expect(exception).to be_a(Grape::Exceptions::InvalidVersionHeader)
274
248
  expect(exception.headers).to eql('X-Cascade' => 'pass')
275
249
  expect(exception.status).to eql 406
276
- expect(exception.message).to include('API vendor or version not found')
250
+ expect(exception.message).to include('API version not found')
251
+ end
252
+ end
253
+ end
254
+
255
+ context 'when there are multiple versions with complex vendor specified with rescue_from :all' do
256
+ subject {
257
+ Class.new(Grape::API) do
258
+ rescue_from :all
259
+ end
260
+ }
261
+
262
+ let(:v1_app) {
263
+ Class.new(Grape::API) do
264
+ version 'v1', using: :header, vendor: 'test.a-cool_resource', cascade: false, strict: true
265
+ content_type :v1_test, 'application/vnd.test.a-cool_resource-v1+json'
266
+ formatter :v1_test, ->(object, _) { object }
267
+ format :v1_test
268
+
269
+ resources :users do
270
+ get :hello do
271
+ 'one'
272
+ end
273
+ end
274
+ end
275
+ }
276
+
277
+ let(:v2_app) {
278
+ Class.new(Grape::API) do
279
+ version 'v2', using: :header, vendor: 'test.a-cool_resource', strict: true
280
+ content_type :v2_test, 'application/vnd.test.a-cool_resource-v2+json'
281
+ formatter :v2_test, ->(object, _) { object }
282
+ format :v2_test
283
+
284
+ resources :users do
285
+ get :hello do
286
+ 'two'
287
+ end
288
+ end
289
+ end
290
+ }
291
+
292
+ def app
293
+ subject.mount v2_app
294
+ subject.mount v1_app
295
+ subject
296
+ end
297
+
298
+ context 'with header versioned endpoints and a rescue_all block defined' do
299
+ it 'responds correctly to a v1 request' do
300
+ versioned_get '/users/hello', 'v1', using: :header, vendor: 'test.a-cool_resource'
301
+ expect(last_response.body).to eq('one')
302
+ expect(last_response.body).not_to include('API vendor or version not found')
303
+ end
304
+
305
+ it 'responds correctly to a v2 request' do
306
+ versioned_get '/users/hello', 'v2', using: :header, vendor: 'test.a-cool_resource'
307
+ expect(last_response.body).to eq('two')
308
+ expect(last_response.body).not_to include('API vendor or version not found')
277
309
  end
278
310
  end
279
311
  end