grape 0.10.1 → 0.11.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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rspec +1 -1
- data/CHANGELOG.md +18 -0
- data/Gemfile +1 -1
- data/README.md +124 -9
- data/UPGRADING.md +66 -0
- data/gemfiles/rails_3.gemfile +1 -1
- data/gemfiles/rails_4.gemfile +1 -1
- data/lib/grape.rb +4 -0
- data/lib/grape/api.rb +1 -5
- data/lib/grape/dsl/inside_route.rb +2 -1
- data/lib/grape/dsl/parameters.rb +20 -9
- data/lib/grape/dsl/routing.rb +11 -1
- data/lib/grape/error_formatter/base.rb +1 -1
- data/lib/grape/exceptions/invalid_accept_header.rb +10 -0
- data/lib/grape/exceptions/invalid_message_body.rb +10 -0
- data/lib/grape/exceptions/missing_group_type.rb +10 -0
- data/lib/grape/exceptions/unsupported_group_type.rb +10 -0
- data/lib/grape/http/request.rb +1 -0
- data/lib/grape/locale/en.yml +11 -0
- data/lib/grape/middleware/base.rb +1 -1
- data/lib/grape/middleware/formatter.rb +2 -0
- data/lib/grape/middleware/versioner/header.rb +14 -11
- data/lib/grape/parser/json.rb +3 -0
- data/lib/grape/parser/xml.rb +3 -0
- data/lib/grape/validations/params_scope.rb +20 -4
- data/lib/grape/validations/validators/coerce.rb +4 -1
- data/lib/grape/validations/validators/values.rb +1 -1
- data/lib/grape/version.rb +1 -1
- data/spec/grape/api_spec.rb +3 -3
- data/spec/grape/dsl/parameters_spec.rb +11 -11
- data/spec/grape/dsl/routing_spec.rb +13 -4
- data/spec/grape/endpoint_spec.rb +2 -2
- data/spec/grape/exceptions/body_parse_errors_spec.rb +105 -0
- data/spec/grape/exceptions/invalid_accept_header_spec.rb +330 -0
- data/spec/grape/integration/rack_spec.rb +32 -0
- data/spec/grape/middleware/base_spec.rb +20 -0
- data/spec/grape/middleware/versioner/header_spec.rb +74 -96
- data/spec/grape/validations/params_scope_spec.rb +124 -0
- data/spec/grape/validations/validators/allow_blank_spec.rb +102 -0
- data/spec/grape/validations/validators/values_spec.rb +45 -1
- data/spec/grape/validations_spec.rb +54 -16
- data/spec/shared/versioning_examples.rb +32 -0
- metadata +61 -51
data/lib/grape/dsl/routing.rb
CHANGED
@@ -35,10 +35,20 @@ module Grape
|
|
35
35
|
fail Grape::Exceptions::MissingVendorOption.new if options[:using] == :header && !options.key?(:vendor)
|
36
36
|
|
37
37
|
@versions = versions | args
|
38
|
-
|
38
|
+
|
39
|
+
if block_given?
|
40
|
+
within_namespace do
|
41
|
+
namespace_inheritable(:version, args)
|
42
|
+
namespace_inheritable(:version_options, options)
|
43
|
+
|
44
|
+
instance_eval(&block)
|
45
|
+
end
|
46
|
+
else
|
39
47
|
namespace_inheritable(:version, args)
|
40
48
|
namespace_inheritable(:version_options, options)
|
41
49
|
end
|
50
|
+
|
51
|
+
# reset_validations!
|
42
52
|
end
|
43
53
|
|
44
54
|
@versions.last unless @versions.nil?
|
@@ -41,7 +41,7 @@ module Grape
|
|
41
41
|
http_codes = env['rack.routing_args'][:route_info].route_http_codes || []
|
42
42
|
found_code = http_codes.find do |http_code|
|
43
43
|
(http_code[0].to_i == env['api.endpoint'].status) && http_code[2].respond_to?(:represent)
|
44
|
-
end
|
44
|
+
end if env['api.endpoint'].request
|
45
45
|
|
46
46
|
presenter = found_code[2] if found_code
|
47
47
|
end
|
data/lib/grape/http/request.rb
CHANGED
data/lib/grape/locale/en.yml
CHANGED
@@ -33,4 +33,15 @@ en:
|
|
33
33
|
at_least_one: 'are missing, at least one parameter must be provided'
|
34
34
|
exactly_one: 'are missing, exactly one parameter must be provided'
|
35
35
|
all_or_none: 'provide all or none of parameters'
|
36
|
+
missing_group_type: 'group type is required'
|
37
|
+
unsupported_group_type: 'group type must be Array or Hash'
|
38
|
+
invalid_message_body:
|
39
|
+
problem: "message body does not match declared format"
|
40
|
+
resolution:
|
41
|
+
"when specifying %{body_format} as content-type, you must pass valid
|
42
|
+
%{body_format} in the request's 'body'
|
43
|
+
"
|
44
|
+
invalid_accept_header:
|
45
|
+
problem: 'Invalid accept header'
|
46
|
+
resolution: '%{message}'
|
36
47
|
|
@@ -16,23 +16,19 @@ module Grape
|
|
16
16
|
# env['api.subtype'] => 'vnd.mycompany-v1+json'
|
17
17
|
# env['api.vendor] => 'mycompany'
|
18
18
|
# env['api.version] => 'v1'
|
19
|
-
# env['api.format] => '
|
19
|
+
# env['api.format] => 'json'
|
20
20
|
#
|
21
21
|
# If version does not match this route, then a 406 is raised with
|
22
22
|
# X-Cascade header to alert Rack::Mount to attempt the next matched
|
23
23
|
# route.
|
24
24
|
class Header < Base
|
25
25
|
def before
|
26
|
-
|
27
|
-
header = Rack::Accept::MediaType.new env['HTTP_ACCEPT']
|
28
|
-
rescue RuntimeError => e
|
29
|
-
throw :error, status: 406, headers: error_headers, message: e.message
|
30
|
-
end
|
26
|
+
header = rack_accept_header
|
31
27
|
|
32
28
|
if strict?
|
33
29
|
# If no Accept header:
|
34
30
|
if header.qvalues.empty?
|
35
|
-
|
31
|
+
fail Grape::Exceptions::InvalidAcceptHeader.new('Accept header must be set.', error_headers)
|
36
32
|
end
|
37
33
|
# Remove any acceptable content types with ranges.
|
38
34
|
header.qvalues.reject! do |media_type, _|
|
@@ -40,7 +36,8 @@ module Grape
|
|
40
36
|
end
|
41
37
|
# If all Accept headers included a range:
|
42
38
|
if header.qvalues.empty?
|
43
|
-
|
39
|
+
fail Grape::Exceptions::InvalidAcceptHeader.new('Accept header must not contain ranges ("*").',
|
40
|
+
error_headers)
|
44
41
|
end
|
45
42
|
end
|
46
43
|
|
@@ -58,10 +55,10 @@ module Grape
|
|
58
55
|
end
|
59
56
|
# If none of the available content types are acceptable:
|
60
57
|
elsif strict?
|
61
|
-
|
58
|
+
fail Grape::Exceptions::InvalidAcceptHeader.new('406 Not Acceptable', error_headers)
|
62
59
|
# If all acceptable content types specify a vendor or version that doesn't exist:
|
63
60
|
elsif header.values.all? { |header_value| has_vendor?(header_value) || version?(header_value) }
|
64
|
-
|
61
|
+
fail Grape::Exceptions::InvalidAcceptHeader.new('API vendor or version not found.', error_headers)
|
65
62
|
end
|
66
63
|
end
|
67
64
|
|
@@ -83,7 +80,13 @@ module Grape
|
|
83
80
|
available_media_types << media_type
|
84
81
|
end
|
85
82
|
|
86
|
-
available_media_types
|
83
|
+
available_media_types.flatten
|
84
|
+
end
|
85
|
+
|
86
|
+
def rack_accept_header
|
87
|
+
Rack::Accept::MediaType.new env['HTTP_ACCEPT']
|
88
|
+
rescue RuntimeError => e
|
89
|
+
raise Grape::Exceptions::InvalidAcceptHeader.new(e.message, error_headers)
|
87
90
|
end
|
88
91
|
|
89
92
|
def versions
|
data/lib/grape/parser/json.rb
CHANGED
@@ -4,6 +4,9 @@ module Grape
|
|
4
4
|
class << self
|
5
5
|
def call(object, env)
|
6
6
|
MultiJson.load(object)
|
7
|
+
rescue MultiJson::ParseError
|
8
|
+
# handle JSON parsing errors via the rescue handlers or provide error message
|
9
|
+
raise Grape::Exceptions::InvalidMessageBody, 'application/json'
|
7
10
|
end
|
8
11
|
end
|
9
12
|
end
|
data/lib/grape/parser/xml.rb
CHANGED
@@ -4,6 +4,9 @@ module Grape
|
|
4
4
|
class << self
|
5
5
|
def call(object, env)
|
6
6
|
MultiXml.parse(object)
|
7
|
+
rescue MultiXml::ParseError
|
8
|
+
# handle XML parsing errors via the rescue handlers or provide error message
|
9
|
+
raise Grape::Exceptions::InvalidMessageBody, 'application/xml'
|
7
10
|
end
|
8
11
|
end
|
9
12
|
end
|
@@ -64,14 +64,29 @@ module Grape
|
|
64
64
|
end
|
65
65
|
end
|
66
66
|
|
67
|
+
def require_optional_fields(context, opts)
|
68
|
+
optional_fields = opts[:using].keys
|
69
|
+
optional_fields -= Array(opts[:except]) unless context == :all
|
70
|
+
optional_fields.each do |field|
|
71
|
+
field_opts = opts[:using][field]
|
72
|
+
optional(field, field_opts) if field_opts
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
67
76
|
def validate_attributes(attrs, opts, &block)
|
68
|
-
validations =
|
69
|
-
validations.merge!(opts) if opts
|
77
|
+
validations = opts.clone
|
70
78
|
validations[:type] ||= Array if block
|
71
79
|
validates(attrs, validations)
|
72
80
|
end
|
73
81
|
|
74
82
|
def new_scope(attrs, optional = false, &block)
|
83
|
+
# if required params are grouped and no type or unsupported type is provided, raise an error
|
84
|
+
type = attrs[1] ? attrs[1][:type] : nil
|
85
|
+
if attrs.first && !optional
|
86
|
+
fail Grape::Exceptions::MissingGroupTypeError.new if type.nil?
|
87
|
+
fail Grape::Exceptions::UnsupportedGroupTypeError.new unless [Array, Hash].include?(type)
|
88
|
+
end
|
89
|
+
|
75
90
|
opts = attrs[1] || { type: Array }
|
76
91
|
ParamsScope.new(api: @api, element: attrs.first, parent: self, optional: optional, type: opts[:type], &block)
|
77
92
|
end
|
@@ -141,7 +156,7 @@ module Grape
|
|
141
156
|
|
142
157
|
def guess_coerce_type(coerce_type, values)
|
143
158
|
return coerce_type if !values || values.is_a?(Proc)
|
144
|
-
return values.first.class if coerce_type == Array && !values.empty?
|
159
|
+
return values.first.class if coerce_type == Array && (values.is_a?(Range) || !values.empty?)
|
145
160
|
coerce_type
|
146
161
|
end
|
147
162
|
|
@@ -167,7 +182,8 @@ module Grape
|
|
167
182
|
return unless coerce_type && values
|
168
183
|
return if values.is_a?(Proc)
|
169
184
|
coerce_type = coerce_type.first if coerce_type.kind_of?(Array)
|
170
|
-
|
185
|
+
value_types = values.is_a?(Range) ? [values.begin, values.end] : values
|
186
|
+
if value_types.any? { |v| !v.kind_of?(coerce_type) }
|
171
187
|
fail Grape::Exceptions::IncompatibleOptionValues.new(:type, coerce_type, :values, values)
|
172
188
|
end
|
173
189
|
end
|
@@ -29,9 +29,12 @@ module Grape
|
|
29
29
|
# allow nil, to ignore when a parameter is absent
|
30
30
|
return true if val.nil?
|
31
31
|
if klass == Virtus::Attribute::Boolean
|
32
|
-
val.is_a?(TrueClass) || val.is_a?(FalseClass)
|
32
|
+
val.is_a?(TrueClass) || val.is_a?(FalseClass) || (val.is_a?(String) && val.empty?)
|
33
33
|
elsif klass == Rack::Multipart::UploadedFile
|
34
34
|
val.is_a?(Hashie::Mash) && val.key?(:tempfile)
|
35
|
+
elsif [DateTime, Date, Numeric].any?{ |vclass| vclass >= klass }
|
36
|
+
return true if val.is_a?(String) && val.empty?
|
37
|
+
val.is_a?(klass)
|
35
38
|
else
|
36
39
|
val.is_a?(klass)
|
37
40
|
end
|
@@ -11,7 +11,7 @@ module Grape
|
|
11
11
|
|
12
12
|
values = @values.is_a?(Proc) ? @values.call : @values
|
13
13
|
param_array = params[attr_name].nil? ? [nil] : Array.wrap(params[attr_name])
|
14
|
-
unless
|
14
|
+
unless param_array.all? { |param| values.include?(param) }
|
15
15
|
fail Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message_key: :values
|
16
16
|
end
|
17
17
|
end
|
data/lib/grape/version.rb
CHANGED
data/spec/grape/api_spec.rb
CHANGED
@@ -2166,11 +2166,11 @@ describe Grape::API do
|
|
2166
2166
|
it 'groups nested params and prevents overwriting of params with same name in different groups' do
|
2167
2167
|
subject.desc 'method'
|
2168
2168
|
subject.params do
|
2169
|
-
group :group1 do
|
2169
|
+
group :group1, type: Array do
|
2170
2170
|
optional :param1, desc: 'group1 param1 desc'
|
2171
2171
|
requires :param2, desc: 'group1 param2 desc'
|
2172
2172
|
end
|
2173
|
-
group :group2 do
|
2173
|
+
group :group2, type: Array do
|
2174
2174
|
optional :param1, desc: 'group2 param1 desc'
|
2175
2175
|
requires :param2, desc: 'group2 param2 desc'
|
2176
2176
|
end
|
@@ -2190,7 +2190,7 @@ describe Grape::API do
|
|
2190
2190
|
subject.desc 'nesting'
|
2191
2191
|
subject.params do
|
2192
2192
|
requires :root_param, desc: 'root param'
|
2193
|
-
group :nested do
|
2193
|
+
group :nested, type: Array do
|
2194
2194
|
requires :nested_param, desc: 'nested param'
|
2195
2195
|
end
|
2196
2196
|
end
|
@@ -11,7 +11,7 @@ module Grape
|
|
11
11
|
end
|
12
12
|
|
13
13
|
# rubocop:disable TrivialAccessors
|
14
|
-
def
|
14
|
+
def validate_attributes_reader
|
15
15
|
@validate_attributes
|
16
16
|
end
|
17
17
|
# rubocop:enable TrivialAccessors
|
@@ -21,7 +21,7 @@ module Grape
|
|
21
21
|
end
|
22
22
|
|
23
23
|
# rubocop:disable TrivialAccessors
|
24
|
-
def
|
24
|
+
def push_declared_params_reader
|
25
25
|
@push_declared_params
|
26
26
|
end
|
27
27
|
# rubocop:enable TrivialAccessors
|
@@ -31,7 +31,7 @@ module Grape
|
|
31
31
|
end
|
32
32
|
|
33
33
|
# rubocop:disable TrivialAccessors
|
34
|
-
def
|
34
|
+
def validates_reader
|
35
35
|
@validates
|
36
36
|
end
|
37
37
|
# rubocop:enable TrivialAccessors
|
@@ -57,8 +57,8 @@ module Grape
|
|
57
57
|
it 'adds a required parameter' do
|
58
58
|
subject.requires :id, type: Integer, desc: 'Identity.'
|
59
59
|
|
60
|
-
expect(subject.
|
61
|
-
expect(subject.
|
60
|
+
expect(subject.validate_attributes_reader).to eq([[:id], { type: Integer, desc: 'Identity.', presence: true }])
|
61
|
+
expect(subject.push_declared_params_reader).to eq([[:id]])
|
62
62
|
end
|
63
63
|
end
|
64
64
|
|
@@ -66,8 +66,8 @@ module Grape
|
|
66
66
|
it 'adds an optional parameter' do
|
67
67
|
subject.optional :id, type: Integer, desc: 'Identity.'
|
68
68
|
|
69
|
-
expect(subject.
|
70
|
-
expect(subject.
|
69
|
+
expect(subject.validate_attributes_reader).to eq([[:id], { type: Integer, desc: 'Identity.' }])
|
70
|
+
expect(subject.push_declared_params_reader).to eq([[:id]])
|
71
71
|
end
|
72
72
|
end
|
73
73
|
|
@@ -75,7 +75,7 @@ module Grape
|
|
75
75
|
it 'adds an mutally exclusive parameter validation' do
|
76
76
|
subject.mutually_exclusive :media, :audio
|
77
77
|
|
78
|
-
expect(subject.
|
78
|
+
expect(subject.validates_reader).to eq([[:media, :audio], { mutual_exclusion: true }])
|
79
79
|
end
|
80
80
|
end
|
81
81
|
|
@@ -83,7 +83,7 @@ module Grape
|
|
83
83
|
it 'adds an exactly of one parameter validation' do
|
84
84
|
subject.exactly_one_of :media, :audio
|
85
85
|
|
86
|
-
expect(subject.
|
86
|
+
expect(subject.validates_reader).to eq([[:media, :audio], { exactly_one_of: true }])
|
87
87
|
end
|
88
88
|
end
|
89
89
|
|
@@ -91,7 +91,7 @@ module Grape
|
|
91
91
|
it 'adds an at least one of parameter validation' do
|
92
92
|
subject.at_least_one_of :media, :audio
|
93
93
|
|
94
|
-
expect(subject.
|
94
|
+
expect(subject.validates_reader).to eq([[:media, :audio], { at_least_one_of: true }])
|
95
95
|
end
|
96
96
|
end
|
97
97
|
|
@@ -99,7 +99,7 @@ module Grape
|
|
99
99
|
it 'adds an all or none of parameter validation' do
|
100
100
|
subject.all_or_none_of :media, :audio
|
101
101
|
|
102
|
-
expect(subject.
|
102
|
+
expect(subject.validates_reader).to eq([[:media, :audio], { all_or_none_of: true }])
|
103
103
|
end
|
104
104
|
end
|
105
105
|
|
@@ -14,8 +14,13 @@ module Grape
|
|
14
14
|
let(:options) { { a: :b } }
|
15
15
|
let(:path) { '/dummy' }
|
16
16
|
|
17
|
-
|
18
|
-
it '
|
17
|
+
describe '.version' do
|
18
|
+
it 'sets a version for route' do
|
19
|
+
version = 'v1'
|
20
|
+
expect(subject).to receive(:namespace_inheritable).with(:version, [version])
|
21
|
+
expect(subject).to receive(:namespace_inheritable).with(:version_options, using: :path)
|
22
|
+
expect(subject.version(version)).to eq(version)
|
23
|
+
end
|
19
24
|
end
|
20
25
|
|
21
26
|
describe '.prefix' do
|
@@ -140,8 +145,12 @@ module Grape
|
|
140
145
|
it 'does some thing'
|
141
146
|
end
|
142
147
|
|
143
|
-
|
144
|
-
it '
|
148
|
+
describe '.versions' do
|
149
|
+
it 'returns last defined version' do
|
150
|
+
subject.version 'v1'
|
151
|
+
subject.version 'v2'
|
152
|
+
expect(subject.version).to eq('v2')
|
153
|
+
end
|
145
154
|
end
|
146
155
|
end
|
147
156
|
end
|
data/spec/grape/endpoint_spec.rb
CHANGED
@@ -386,9 +386,9 @@ describe Grape::Endpoint do
|
|
386
386
|
|
387
387
|
expect(last_response.status).to eq(200)
|
388
388
|
expect(inner_params[:first]).to eq 'present'
|
389
|
-
expect(inner_params[:nested].keys).to eq
|
389
|
+
expect(inner_params[:nested].keys).to eq %w(fourth fifth nested_nested)
|
390
390
|
expect(inner_params[:nested][:fourth]).to eq ''
|
391
|
-
expect(inner_params[:nested][:nested_nested].keys).to eq
|
391
|
+
expect(inner_params[:nested][:nested_nested].keys).to eq %w(sixth seven)
|
392
392
|
expect(inner_params[:nested][:nested_nested][:sixth]).to eq 'sixth'
|
393
393
|
end
|
394
394
|
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Grape::Exceptions::ValidationErrors do
|
4
|
+
context 'api with rescue_from :all handler' do
|
5
|
+
subject { Class.new(Grape::API) }
|
6
|
+
before {
|
7
|
+
subject.rescue_from :all do |e|
|
8
|
+
rack_response 'message was processed', 400
|
9
|
+
end
|
10
|
+
subject.params do
|
11
|
+
requires :beer
|
12
|
+
end
|
13
|
+
subject.post '/beer' do
|
14
|
+
'beer received'
|
15
|
+
end
|
16
|
+
}
|
17
|
+
|
18
|
+
def app
|
19
|
+
subject
|
20
|
+
end
|
21
|
+
|
22
|
+
context 'with content_type json' do
|
23
|
+
it 'can recover from failed body parsing' do
|
24
|
+
post '/beer', 'test', 'CONTENT_TYPE' => 'application/json'
|
25
|
+
expect(last_response.status).to eq 400
|
26
|
+
expect(last_response.body).to eq('message was processed')
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'with content_type xml' do
|
31
|
+
it 'can recover from failed body parsing' do
|
32
|
+
post '/beer', 'test', 'CONTENT_TYPE' => 'application/xml'
|
33
|
+
expect(last_response.status).to eq 400
|
34
|
+
expect(last_response.body).to eq('message was processed')
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'with content_type text' do
|
39
|
+
it 'can recover from failed body parsing' do
|
40
|
+
post '/beer', 'test', 'CONTENT_TYPE' => 'text/plain'
|
41
|
+
expect(last_response.status).to eq 400
|
42
|
+
expect(last_response.body).to eq('message was processed')
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'with no specific content_type' do
|
47
|
+
it 'can recover from failed body parsing' do
|
48
|
+
post '/beer', 'test', {}
|
49
|
+
expect(last_response.status).to eq 400
|
50
|
+
expect(last_response.body).to eq('message was processed')
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context 'api without a rescue handler' do
|
56
|
+
subject { Class.new(Grape::API) }
|
57
|
+
before {
|
58
|
+
subject.params do
|
59
|
+
requires :beer
|
60
|
+
end
|
61
|
+
subject.post '/beer' do
|
62
|
+
'beer received'
|
63
|
+
end
|
64
|
+
}
|
65
|
+
|
66
|
+
def app
|
67
|
+
subject
|
68
|
+
end
|
69
|
+
|
70
|
+
context 'and with content_type json' do
|
71
|
+
it 'can recover from failed body parsing' do
|
72
|
+
post '/beer', 'test', 'CONTENT_TYPE' => 'application/json'
|
73
|
+
expect(last_response.status).to eq 400
|
74
|
+
expect(last_response.body).to include('message body does not match declared format')
|
75
|
+
expect(last_response.body).to include('application/json')
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
context 'with content_type xml' do
|
80
|
+
it 'can recover from failed body parsing' do
|
81
|
+
post '/beer', 'test', 'CONTENT_TYPE' => 'application/xml'
|
82
|
+
expect(last_response.status).to eq 400
|
83
|
+
expect(last_response.body).to include('message body does not match declared format')
|
84
|
+
expect(last_response.body).to include('application/xml')
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
context 'with content_type text' do
|
89
|
+
it 'can recover from failed body parsing' do
|
90
|
+
post '/beer', 'test', 'CONTENT_TYPE' => 'text/plain'
|
91
|
+
expect(last_response.status).to eq 400
|
92
|
+
expect(last_response.body).to eq('beer is missing')
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
context 'and with no specific content_type' do
|
97
|
+
it 'can recover from failed body parsing' do
|
98
|
+
post '/beer', 'test', {}
|
99
|
+
expect(last_response.status).to eq 400
|
100
|
+
# plain response with text/html
|
101
|
+
expect(last_response.body).to eq('beer is missing')
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|