grape 1.8.0 → 2.0.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -0
- data/README.md +19 -22
- data/UPGRADING.md +35 -0
- data/grape.gemspec +1 -4
- data/lib/grape/dsl/desc.rb +1 -1
- data/lib/grape/dsl/inside_route.rb +9 -9
- data/lib/grape/endpoint.rb +9 -1
- data/lib/grape/exceptions/missing_group_type.rb +1 -1
- data/lib/grape/exceptions/unsupported_group_type.rb +1 -1
- data/lib/grape/http/headers.rb +12 -2
- data/lib/grape/middleware/auth/strategies.rb +1 -2
- data/lib/grape/middleware/error.rb +4 -4
- data/lib/grape/middleware/formatter.rb +5 -5
- data/lib/grape/railtie.rb +9 -0
- data/lib/grape/request.rb +8 -2
- data/lib/grape/router/route.rb +1 -1
- data/lib/grape/validations/validators/base.rb +1 -1
- data/lib/grape/validations/validators/values_validator.rb +2 -2
- data/lib/grape/version.rb +1 -1
- data/lib/grape.rb +15 -2
- metadata +8 -243
- data/spec/grape/api/custom_validations_spec.rb +0 -213
- data/spec/grape/api/deeply_included_options_spec.rb +0 -56
- data/spec/grape/api/defines_boolean_in_params_spec.rb +0 -38
- data/spec/grape/api/documentation_spec.rb +0 -59
- data/spec/grape/api/inherited_helpers_spec.rb +0 -114
- data/spec/grape/api/instance_spec.rb +0 -103
- data/spec/grape/api/invalid_format_spec.rb +0 -45
- data/spec/grape/api/namespace_parameters_in_route_spec.rb +0 -38
- data/spec/grape/api/nested_helpers_spec.rb +0 -50
- data/spec/grape/api/optional_parameters_in_route_spec.rb +0 -43
- data/spec/grape/api/parameters_modification_spec.rb +0 -41
- data/spec/grape/api/patch_method_helpers_spec.rb +0 -79
- data/spec/grape/api/recognize_path_spec.rb +0 -21
- data/spec/grape/api/required_parameters_in_route_spec.rb +0 -37
- data/spec/grape/api/required_parameters_with_invalid_method_spec.rb +0 -26
- data/spec/grape/api/routes_with_requirements_spec.rb +0 -59
- data/spec/grape/api/shared_helpers_exactly_one_of_spec.rb +0 -41
- data/spec/grape/api/shared_helpers_spec.rb +0 -36
- data/spec/grape/api_remount_spec.rb +0 -509
- data/spec/grape/api_spec.rb +0 -4356
- data/spec/grape/dsl/callbacks_spec.rb +0 -45
- data/spec/grape/dsl/desc_spec.rb +0 -98
- data/spec/grape/dsl/headers_spec.rb +0 -62
- data/spec/grape/dsl/helpers_spec.rb +0 -100
- data/spec/grape/dsl/inside_route_spec.rb +0 -531
- data/spec/grape/dsl/logger_spec.rb +0 -24
- data/spec/grape/dsl/middleware_spec.rb +0 -60
- data/spec/grape/dsl/parameters_spec.rb +0 -180
- data/spec/grape/dsl/request_response_spec.rb +0 -225
- data/spec/grape/dsl/routing_spec.rb +0 -275
- data/spec/grape/dsl/settings_spec.rb +0 -261
- data/spec/grape/dsl/validations_spec.rb +0 -55
- data/spec/grape/endpoint/declared_spec.rb +0 -846
- data/spec/grape/endpoint_spec.rb +0 -1085
- data/spec/grape/entity_spec.rb +0 -336
- data/spec/grape/exceptions/base_spec.rb +0 -81
- data/spec/grape/exceptions/body_parse_errors_spec.rb +0 -185
- data/spec/grape/exceptions/invalid_accept_header_spec.rb +0 -358
- data/spec/grape/exceptions/invalid_formatter_spec.rb +0 -15
- data/spec/grape/exceptions/invalid_response_spec.rb +0 -11
- data/spec/grape/exceptions/invalid_versioner_option_spec.rb +0 -15
- data/spec/grape/exceptions/missing_group_type_spec.rb +0 -17
- data/spec/grape/exceptions/missing_mime_type_spec.rb +0 -17
- data/spec/grape/exceptions/missing_option_spec.rb +0 -15
- data/spec/grape/exceptions/unknown_options_spec.rb +0 -15
- data/spec/grape/exceptions/unknown_validator_spec.rb +0 -15
- data/spec/grape/exceptions/unsupported_group_type_spec.rb +0 -19
- data/spec/grape/exceptions/validation_errors_spec.rb +0 -92
- data/spec/grape/exceptions/validation_spec.rb +0 -19
- data/spec/grape/extensions/param_builders/hash_spec.rb +0 -83
- data/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb +0 -105
- data/spec/grape/extensions/param_builders/hashie/mash_spec.rb +0 -79
- data/spec/grape/grape_spec.rb +0 -9
- data/spec/grape/integration/global_namespace_function_spec.rb +0 -29
- data/spec/grape/integration/rack_sendfile_spec.rb +0 -48
- data/spec/grape/integration/rack_spec.rb +0 -51
- data/spec/grape/loading_spec.rb +0 -44
- data/spec/grape/middleware/auth/base_spec.rb +0 -31
- data/spec/grape/middleware/auth/dsl_spec.rb +0 -60
- data/spec/grape/middleware/auth/strategies_spec.rb +0 -120
- data/spec/grape/middleware/base_spec.rb +0 -221
- data/spec/grape/middleware/error_spec.rb +0 -85
- data/spec/grape/middleware/exception_spec.rb +0 -294
- data/spec/grape/middleware/formatter_spec.rb +0 -461
- data/spec/grape/middleware/globals_spec.rb +0 -30
- data/spec/grape/middleware/stack_spec.rb +0 -155
- data/spec/grape/middleware/versioner/accept_version_header_spec.rb +0 -122
- data/spec/grape/middleware/versioner/header_spec.rb +0 -345
- data/spec/grape/middleware/versioner/param_spec.rb +0 -171
- data/spec/grape/middleware/versioner/path_spec.rb +0 -62
- data/spec/grape/middleware/versioner_spec.rb +0 -21
- data/spec/grape/named_api_spec.rb +0 -19
- data/spec/grape/parser_spec.rb +0 -86
- data/spec/grape/path_spec.rb +0 -252
- data/spec/grape/presenters/presenter_spec.rb +0 -71
- data/spec/grape/request_spec.rb +0 -126
- data/spec/grape/util/inheritable_setting_spec.rb +0 -242
- data/spec/grape/util/inheritable_values_spec.rb +0 -79
- data/spec/grape/util/reverse_stackable_values_spec.rb +0 -134
- data/spec/grape/util/stackable_values_spec.rb +0 -128
- data/spec/grape/util/strict_hash_configuration_spec.rb +0 -38
- data/spec/grape/validations/attributes_doc_spec.rb +0 -153
- data/spec/grape/validations/instance_behaivour_spec.rb +0 -43
- data/spec/grape/validations/multiple_attributes_iterator_spec.rb +0 -38
- data/spec/grape/validations/params_scope_spec.rb +0 -1420
- data/spec/grape/validations/single_attribute_iterator_spec.rb +0 -56
- data/spec/grape/validations/types/array_coercer_spec.rb +0 -33
- data/spec/grape/validations/types/primitive_coercer_spec.rb +0 -150
- data/spec/grape/validations/types/set_coercer_spec.rb +0 -32
- data/spec/grape/validations/types_spec.rb +0 -111
- data/spec/grape/validations/validators/all_or_none_spec.rb +0 -162
- data/spec/grape/validations/validators/allow_blank_spec.rb +0 -575
- data/spec/grape/validations/validators/at_least_one_of_spec.rb +0 -205
- data/spec/grape/validations/validators/base_spec.rb +0 -38
- data/spec/grape/validations/validators/coerce_spec.rb +0 -1261
- data/spec/grape/validations/validators/default_spec.rb +0 -463
- data/spec/grape/validations/validators/exactly_one_of_spec.rb +0 -233
- data/spec/grape/validations/validators/except_values_spec.rb +0 -192
- data/spec/grape/validations/validators/mutual_exclusion_spec.rb +0 -214
- data/spec/grape/validations/validators/presence_spec.rb +0 -315
- data/spec/grape/validations/validators/regexp_spec.rb +0 -161
- data/spec/grape/validations/validators/same_as_spec.rb +0 -57
- data/spec/grape/validations/validators/values_spec.rb +0 -733
- data/spec/grape/validations/validators/zh-CN.yml +0 -10
- data/spec/grape/validations_spec.rb +0 -2030
- data/spec/integration/eager_load/eager_load_spec.rb +0 -15
- data/spec/integration/multi_json/json_spec.rb +0 -7
- data/spec/integration/multi_xml/xml_spec.rb +0 -7
- data/spec/shared/deprecated_class_examples.rb +0 -16
- data/spec/shared/versioning_examples.rb +0 -215
- data/spec/spec_helper.rb +0 -52
- data/spec/support/basic_auth_encode_helpers.rb +0 -11
- data/spec/support/chunks.rb +0 -14
- data/spec/support/content_type_helpers.rb +0 -15
- data/spec/support/endpoint_faker.rb +0 -25
- data/spec/support/file_streamer.rb +0 -13
- data/spec/support/integer_helpers.rb +0 -13
- data/spec/support/versioned_helpers.rb +0 -55
@@ -1,461 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
describe Grape::Middleware::Formatter do
|
4
|
-
subject { described_class.new(app) }
|
5
|
-
|
6
|
-
before { allow(subject).to receive(:dup).and_return(subject) }
|
7
|
-
|
8
|
-
let(:body) { { 'foo' => 'bar' } }
|
9
|
-
let(:app) { ->(_env) { [200, {}, [body]] } }
|
10
|
-
|
11
|
-
context 'serialization' do
|
12
|
-
let(:body) { { 'abc' => 'def' } }
|
13
|
-
|
14
|
-
it 'looks at the bodies for possibly serializable data' do
|
15
|
-
_, _, bodies = *subject.call('PATH_INFO' => '/somewhere', 'HTTP_ACCEPT' => 'application/json')
|
16
|
-
bodies.each { |b| expect(b).to eq(::Grape::Json.dump(body)) } # rubocop:disable RSpec/IteratedExpectation
|
17
|
-
end
|
18
|
-
|
19
|
-
context 'default format' do
|
20
|
-
let(:body) { ['foo'] }
|
21
|
-
|
22
|
-
it 'calls #to_json since default format is json' do
|
23
|
-
body.instance_eval do
|
24
|
-
def to_json(*_args)
|
25
|
-
'"bar"'
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
subject.call('PATH_INFO' => '/somewhere', 'HTTP_ACCEPT' => 'application/json').to_a.last.each { |b| expect(b).to eq('"bar"') } # rubocop:disable RSpec/IteratedExpectation
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
context 'jsonapi' do
|
34
|
-
let(:body) { { 'foos' => [{ 'bar' => 'baz' }] } }
|
35
|
-
|
36
|
-
it 'calls #to_json if the content type is jsonapi' do
|
37
|
-
body.instance_eval do
|
38
|
-
def to_json(*_args)
|
39
|
-
'{"foos":[{"bar":"baz"}] }'
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
subject.call('PATH_INFO' => '/somewhere', 'HTTP_ACCEPT' => 'application/vnd.api+json').to_a.last.each { |b| expect(b).to eq('{"foos":[{"bar":"baz"}] }') } # rubocop:disable RSpec/IteratedExpectation
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
context 'xml' do
|
48
|
-
let(:body) { +'string' }
|
49
|
-
|
50
|
-
it 'calls #to_xml if the content type is xml' do
|
51
|
-
body.instance_eval do
|
52
|
-
def to_xml
|
53
|
-
'<bar/>'
|
54
|
-
end
|
55
|
-
end
|
56
|
-
subject.call('PATH_INFO' => '/somewhere.xml', 'HTTP_ACCEPT' => 'application/json').to_a.last.each { |b| expect(b).to eq('<bar/>') } # rubocop:disable RSpec/IteratedExpectation
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
context 'error handling' do
|
62
|
-
let(:formatter) { double(:formatter) }
|
63
|
-
|
64
|
-
before do
|
65
|
-
allow(Grape::Formatter).to receive(:formatter_for) { formatter }
|
66
|
-
end
|
67
|
-
|
68
|
-
it 'rescues formatter-specific exceptions' do
|
69
|
-
allow(formatter).to receive(:call) { raise Grape::Exceptions::InvalidFormatter.new(String, 'xml') }
|
70
|
-
|
71
|
-
expect do
|
72
|
-
catch(:error) { subject.call('PATH_INFO' => '/somewhere.xml', 'HTTP_ACCEPT' => 'application/json') }
|
73
|
-
end.not_to raise_error
|
74
|
-
end
|
75
|
-
|
76
|
-
it 'does not rescue other exceptions' do
|
77
|
-
allow(formatter).to receive(:call) { raise StandardError }
|
78
|
-
|
79
|
-
expect do
|
80
|
-
catch(:error) { subject.call('PATH_INFO' => '/somewhere.xml', 'HTTP_ACCEPT' => 'application/json') }
|
81
|
-
end.to raise_error(StandardError)
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
context 'detection' do
|
86
|
-
it 'uses the xml extension if one is provided' do
|
87
|
-
subject.call('PATH_INFO' => '/info.xml')
|
88
|
-
expect(subject.env['api.format']).to eq(:xml)
|
89
|
-
end
|
90
|
-
|
91
|
-
it 'uses the json extension if one is provided' do
|
92
|
-
subject.call('PATH_INFO' => '/info.json')
|
93
|
-
expect(subject.env['api.format']).to eq(:json)
|
94
|
-
end
|
95
|
-
|
96
|
-
it 'uses the format parameter if one is provided' do
|
97
|
-
subject.call('PATH_INFO' => '/info', 'QUERY_STRING' => 'format=json')
|
98
|
-
expect(subject.env['api.format']).to eq(:json)
|
99
|
-
subject.call('PATH_INFO' => '/info', 'QUERY_STRING' => 'format=xml')
|
100
|
-
expect(subject.env['api.format']).to eq(:xml)
|
101
|
-
end
|
102
|
-
|
103
|
-
it 'uses the default format if none is provided' do
|
104
|
-
subject.call('PATH_INFO' => '/info')
|
105
|
-
expect(subject.env['api.format']).to eq(:txt)
|
106
|
-
end
|
107
|
-
|
108
|
-
it 'uses the requested format if provided in headers' do
|
109
|
-
subject.call('PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/json')
|
110
|
-
expect(subject.env['api.format']).to eq(:json)
|
111
|
-
end
|
112
|
-
|
113
|
-
it 'uses the file extension format if provided before headers' do
|
114
|
-
subject.call('PATH_INFO' => '/info.txt', 'HTTP_ACCEPT' => 'application/json')
|
115
|
-
expect(subject.env['api.format']).to eq(:txt)
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
context 'accept header detection' do
|
120
|
-
it 'detects from the Accept header' do
|
121
|
-
subject.call('PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/xml')
|
122
|
-
expect(subject.env['api.format']).to eq(:xml)
|
123
|
-
end
|
124
|
-
|
125
|
-
it 'uses quality rankings to determine formats' do
|
126
|
-
subject.call('PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/json; q=0.3,application/xml; q=1.0')
|
127
|
-
expect(subject.env['api.format']).to eq(:xml)
|
128
|
-
subject.call('PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/json; q=1.0,application/xml; q=0.3')
|
129
|
-
expect(subject.env['api.format']).to eq(:json)
|
130
|
-
end
|
131
|
-
|
132
|
-
it 'handles quality rankings mixed with nothing' do
|
133
|
-
subject.call('PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/json,application/xml; q=1.0')
|
134
|
-
expect(subject.env['api.format']).to eq(:xml)
|
135
|
-
end
|
136
|
-
|
137
|
-
it 'parses headers with other attributes' do
|
138
|
-
subject.call('PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/json; abc=2.3; q=1.0,application/xml; q=0.7')
|
139
|
-
expect(subject.env['api.format']).to eq(:json)
|
140
|
-
end
|
141
|
-
|
142
|
-
it 'parses headers with vendor and api version' do
|
143
|
-
subject.call('PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/vnd.test-v1+xml')
|
144
|
-
expect(subject.env['api.format']).to eq(:xml)
|
145
|
-
end
|
146
|
-
|
147
|
-
context 'with custom vendored content types' do
|
148
|
-
before do
|
149
|
-
subject.options[:content_types] = {}
|
150
|
-
subject.options[:content_types][:custom] = 'application/vnd.test+json'
|
151
|
-
end
|
152
|
-
|
153
|
-
it 'uses the custom type' do
|
154
|
-
subject.call('PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/vnd.test+json')
|
155
|
-
expect(subject.env['api.format']).to eq(:custom)
|
156
|
-
end
|
157
|
-
end
|
158
|
-
|
159
|
-
it 'parses headers with symbols as hash keys' do
|
160
|
-
subject.call('PATH_INFO' => '/info', 'http_accept' => 'application/xml', system_time: '091293')
|
161
|
-
expect(subject.env[:system_time]).to eq('091293')
|
162
|
-
end
|
163
|
-
end
|
164
|
-
|
165
|
-
context 'content-type' do
|
166
|
-
it 'is set for json' do
|
167
|
-
_, headers, = subject.call('PATH_INFO' => '/info.json')
|
168
|
-
expect(headers['Content-type']).to eq('application/json')
|
169
|
-
end
|
170
|
-
|
171
|
-
it 'is set for xml' do
|
172
|
-
_, headers, = subject.call('PATH_INFO' => '/info.xml')
|
173
|
-
expect(headers['Content-type']).to eq('application/xml')
|
174
|
-
end
|
175
|
-
|
176
|
-
it 'is set for txt' do
|
177
|
-
_, headers, = subject.call('PATH_INFO' => '/info.txt')
|
178
|
-
expect(headers['Content-type']).to eq('text/plain')
|
179
|
-
end
|
180
|
-
|
181
|
-
it 'is set for custom' do
|
182
|
-
subject.options[:content_types] = {}
|
183
|
-
subject.options[:content_types][:custom] = 'application/x-custom'
|
184
|
-
_, headers, = subject.call('PATH_INFO' => '/info.custom')
|
185
|
-
expect(headers['Content-type']).to eq('application/x-custom')
|
186
|
-
end
|
187
|
-
|
188
|
-
it 'is set for vendored with registered type' do
|
189
|
-
subject.options[:content_types] = {}
|
190
|
-
subject.options[:content_types][:custom] = 'application/vnd.test+json'
|
191
|
-
_, headers, = subject.call('PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/vnd.test+json')
|
192
|
-
expect(headers['Content-type']).to eq('application/vnd.test+json')
|
193
|
-
end
|
194
|
-
|
195
|
-
it 'is set to closest generic for custom vendored/versioned without registered type' do
|
196
|
-
_, headers, = subject.call('PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/vnd.test+json')
|
197
|
-
expect(headers['Content-type']).to eq('application/json')
|
198
|
-
end
|
199
|
-
end
|
200
|
-
|
201
|
-
context 'format' do
|
202
|
-
it 'uses custom formatter' do
|
203
|
-
subject.options[:content_types] = {}
|
204
|
-
subject.options[:content_types][:custom] = "don't care"
|
205
|
-
subject.options[:formatters][:custom] = ->(_obj, _env) { 'CUSTOM FORMAT' }
|
206
|
-
_, _, body = subject.call('PATH_INFO' => '/info.custom')
|
207
|
-
expect(read_chunks(body)).to eq(['CUSTOM FORMAT'])
|
208
|
-
end
|
209
|
-
|
210
|
-
context 'default' do
|
211
|
-
let(:body) { ['blah'] }
|
212
|
-
|
213
|
-
it 'uses default json formatter' do
|
214
|
-
_, _, body = subject.call('PATH_INFO' => '/info.json')
|
215
|
-
expect(read_chunks(body)).to eq(['["blah"]'])
|
216
|
-
end
|
217
|
-
end
|
218
|
-
|
219
|
-
it 'uses custom json formatter' do
|
220
|
-
subject.options[:formatters][:json] = ->(_obj, _env) { 'CUSTOM JSON FORMAT' }
|
221
|
-
_, _, body = subject.call('PATH_INFO' => '/info.json')
|
222
|
-
expect(read_chunks(body)).to eq(['CUSTOM JSON FORMAT'])
|
223
|
-
end
|
224
|
-
end
|
225
|
-
|
226
|
-
context 'no content responses' do
|
227
|
-
let(:no_content_response) { ->(status) { [status, {}, ['']] } }
|
228
|
-
|
229
|
-
STATUSES_WITHOUT_BODY = if Gem::Version.new(Rack.release) >= Gem::Version.new('2.1.0')
|
230
|
-
Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.keys
|
231
|
-
else
|
232
|
-
Rack::Utils::STATUS_WITH_NO_ENTITY_BODY
|
233
|
-
end
|
234
|
-
|
235
|
-
STATUSES_WITHOUT_BODY.each do |status|
|
236
|
-
it "does not modify a #{status} response" do
|
237
|
-
expected_response = no_content_response[status]
|
238
|
-
allow(app).to receive(:call).and_return(expected_response)
|
239
|
-
expect(subject.call({})).to eq(expected_response)
|
240
|
-
end
|
241
|
-
end
|
242
|
-
end
|
243
|
-
|
244
|
-
context 'input' do
|
245
|
-
content_types = ['application/json', 'application/json; charset=utf-8'].freeze
|
246
|
-
%w[POST PATCH PUT DELETE].each do |method|
|
247
|
-
context 'when body is not nil or empty' do
|
248
|
-
context 'when Content-Type is supported' do
|
249
|
-
let(:io) { StringIO.new('{"is_boolean":true,"string":"thing"}') }
|
250
|
-
let(:content_type) { 'application/json' }
|
251
|
-
|
252
|
-
it "parses the body from #{method} and copies values into rack.request.form_hash" do
|
253
|
-
subject.call(
|
254
|
-
'PATH_INFO' => '/info',
|
255
|
-
'REQUEST_METHOD' => method,
|
256
|
-
'CONTENT_TYPE' => content_type,
|
257
|
-
'rack.input' => io,
|
258
|
-
'CONTENT_LENGTH' => io.length
|
259
|
-
)
|
260
|
-
expect(subject.env['rack.request.form_hash']['is_boolean']).to be true
|
261
|
-
expect(subject.env['rack.request.form_hash']['string']).to eq('thing')
|
262
|
-
end
|
263
|
-
end
|
264
|
-
|
265
|
-
context 'when Content-Type is not supported' do
|
266
|
-
let(:io) { StringIO.new('{"is_boolean":true,"string":"thing"}') }
|
267
|
-
let(:content_type) { 'application/atom+xml' }
|
268
|
-
|
269
|
-
it 'returns a 415 HTTP error status' do
|
270
|
-
error = catch(:error) do
|
271
|
-
subject.call(
|
272
|
-
'PATH_INFO' => '/info',
|
273
|
-
'REQUEST_METHOD' => method,
|
274
|
-
'CONTENT_TYPE' => content_type,
|
275
|
-
'rack.input' => io,
|
276
|
-
'CONTENT_LENGTH' => io.length
|
277
|
-
)
|
278
|
-
end
|
279
|
-
expect(error[:status]).to eq(415)
|
280
|
-
expect(error[:message]).to eq("The provided content-type 'application/atom+xml' is not supported.")
|
281
|
-
end
|
282
|
-
end
|
283
|
-
end
|
284
|
-
|
285
|
-
context 'when body is nil' do
|
286
|
-
let(:io) { double }
|
287
|
-
|
288
|
-
before do
|
289
|
-
allow(io).to receive_message_chain(:rewind, :read).and_return(nil)
|
290
|
-
end
|
291
|
-
|
292
|
-
it 'does not read and parse the body' do
|
293
|
-
expect(subject).not_to receive(:read_rack_input)
|
294
|
-
subject.call(
|
295
|
-
'PATH_INFO' => '/info',
|
296
|
-
'REQUEST_METHOD' => method,
|
297
|
-
'CONTENT_TYPE' => 'application/json',
|
298
|
-
'rack.input' => io,
|
299
|
-
'CONTENT_LENGTH' => 0
|
300
|
-
)
|
301
|
-
end
|
302
|
-
end
|
303
|
-
|
304
|
-
context 'when body is empty' do
|
305
|
-
let(:io) { double }
|
306
|
-
|
307
|
-
before do
|
308
|
-
allow(io).to receive_message_chain(:rewind, :read).and_return('')
|
309
|
-
end
|
310
|
-
|
311
|
-
it 'does not read and parse the body' do
|
312
|
-
expect(subject).not_to receive(:read_rack_input)
|
313
|
-
subject.call(
|
314
|
-
'PATH_INFO' => '/info',
|
315
|
-
'REQUEST_METHOD' => method,
|
316
|
-
'CONTENT_TYPE' => 'application/json',
|
317
|
-
'rack.input' => io,
|
318
|
-
'CONTENT_LENGTH' => 0
|
319
|
-
)
|
320
|
-
end
|
321
|
-
end
|
322
|
-
|
323
|
-
content_types.each do |content_type|
|
324
|
-
context content_type do
|
325
|
-
it "parses the body from #{method} and copies values into rack.request.form_hash" do
|
326
|
-
io = StringIO.new('{"is_boolean":true,"string":"thing"}')
|
327
|
-
subject.call(
|
328
|
-
'PATH_INFO' => '/info',
|
329
|
-
'REQUEST_METHOD' => method,
|
330
|
-
'CONTENT_TYPE' => content_type,
|
331
|
-
'rack.input' => io,
|
332
|
-
'CONTENT_LENGTH' => io.length
|
333
|
-
)
|
334
|
-
expect(subject.env['rack.request.form_hash']['is_boolean']).to be true
|
335
|
-
expect(subject.env['rack.request.form_hash']['string']).to eq('thing')
|
336
|
-
end
|
337
|
-
end
|
338
|
-
end
|
339
|
-
it "parses the chunked body from #{method} and copies values into rack.request.from_hash" do
|
340
|
-
io = StringIO.new('{"is_boolean":true,"string":"thing"}')
|
341
|
-
subject.call(
|
342
|
-
'PATH_INFO' => '/infol',
|
343
|
-
'REQUEST_METHOD' => method,
|
344
|
-
'CONTENT_TYPE' => 'application/json',
|
345
|
-
'rack.input' => io,
|
346
|
-
'HTTP_TRANSFER_ENCODING' => 'chunked'
|
347
|
-
)
|
348
|
-
expect(subject.env['rack.request.form_hash']['is_boolean']).to be true
|
349
|
-
expect(subject.env['rack.request.form_hash']['string']).to eq('thing')
|
350
|
-
end
|
351
|
-
|
352
|
-
it 'rewinds IO' do
|
353
|
-
io = StringIO.new('{"is_boolean":true,"string":"thing"}')
|
354
|
-
io.read
|
355
|
-
subject.call(
|
356
|
-
'PATH_INFO' => '/infol',
|
357
|
-
'REQUEST_METHOD' => method,
|
358
|
-
'CONTENT_TYPE' => 'application/json',
|
359
|
-
'rack.input' => io,
|
360
|
-
'HTTP_TRANSFER_ENCODING' => 'chunked'
|
361
|
-
)
|
362
|
-
expect(subject.env['rack.request.form_hash']['is_boolean']).to be true
|
363
|
-
expect(subject.env['rack.request.form_hash']['string']).to eq('thing')
|
364
|
-
end
|
365
|
-
|
366
|
-
it "parses the body from an xml #{method} and copies values into rack.request.from_hash" do
|
367
|
-
io = StringIO.new('<thing><name>Test</name></thing>')
|
368
|
-
subject.call(
|
369
|
-
'PATH_INFO' => '/info.xml',
|
370
|
-
'REQUEST_METHOD' => method,
|
371
|
-
'CONTENT_TYPE' => 'application/xml',
|
372
|
-
'rack.input' => io,
|
373
|
-
'CONTENT_LENGTH' => io.length
|
374
|
-
)
|
375
|
-
if Object.const_defined? :MultiXml
|
376
|
-
expect(subject.env['rack.request.form_hash']['thing']['name']).to eq('Test')
|
377
|
-
else
|
378
|
-
expect(subject.env['rack.request.form_hash']['thing']['name']['__content__']).to eq('Test')
|
379
|
-
end
|
380
|
-
end
|
381
|
-
|
382
|
-
[Rack::Request::FORM_DATA_MEDIA_TYPES, Rack::Request::PARSEABLE_DATA_MEDIA_TYPES].flatten.each do |content_type|
|
383
|
-
it "ignores #{content_type}" do
|
384
|
-
io = StringIO.new('name=Other+Test+Thing')
|
385
|
-
subject.call(
|
386
|
-
'PATH_INFO' => '/info',
|
387
|
-
'REQUEST_METHOD' => method,
|
388
|
-
'CONTENT_TYPE' => content_type,
|
389
|
-
'rack.input' => io,
|
390
|
-
'CONTENT_LENGTH' => io.length
|
391
|
-
)
|
392
|
-
expect(subject.env['rack.request.form_hash']).to be_nil
|
393
|
-
end
|
394
|
-
end
|
395
|
-
end
|
396
|
-
end
|
397
|
-
|
398
|
-
context 'send file' do
|
399
|
-
let(:file) { double(File) }
|
400
|
-
let(:file_body) { Grape::ServeStream::StreamResponse.new(file) }
|
401
|
-
let(:app) { ->(_env) { [200, {}, file_body] } }
|
402
|
-
|
403
|
-
it 'returns a file response' do
|
404
|
-
expect(file).to receive(:each).and_yield('data')
|
405
|
-
env = { 'PATH_INFO' => '/somewhere', 'HTTP_ACCEPT' => 'application/json' }
|
406
|
-
status, headers, body = subject.call(env)
|
407
|
-
expect(status).to be == 200
|
408
|
-
expect(headers.transform_keys(&:downcase)).to be == { 'content-type' => 'application/json' }
|
409
|
-
expect(read_chunks(body)).to be == ['data']
|
410
|
-
end
|
411
|
-
end
|
412
|
-
|
413
|
-
context 'inheritable formatters' do
|
414
|
-
class InvalidFormatter
|
415
|
-
def self.call(_, _)
|
416
|
-
{ message: 'invalid' }.to_json
|
417
|
-
end
|
418
|
-
end
|
419
|
-
let(:app) { ->(_env) { [200, {}, ['']] } }
|
420
|
-
|
421
|
-
before do
|
422
|
-
Grape::Formatter.register :invalid, InvalidFormatter
|
423
|
-
Grape::ContentTypes.register :invalid, 'application/x-invalid'
|
424
|
-
end
|
425
|
-
|
426
|
-
after do
|
427
|
-
Grape::ContentTypes.default_elements.delete(:invalid)
|
428
|
-
Grape::Formatter.default_elements.delete(:invalid)
|
429
|
-
end
|
430
|
-
|
431
|
-
it 'returns response by invalid formatter' do
|
432
|
-
env = { 'PATH_INFO' => '/hello.invalid', 'HTTP_ACCEPT' => 'application/x-invalid' }
|
433
|
-
_, _, body = *subject.call(env)
|
434
|
-
expect(read_chunks(body).join).to eq({ message: 'invalid' }.to_json)
|
435
|
-
end
|
436
|
-
end
|
437
|
-
|
438
|
-
context 'custom parser raises exception and rescue options are enabled for backtrace and original_exception' do
|
439
|
-
it 'adds the backtrace and original_exception to the error output' do
|
440
|
-
subject = described_class.new(
|
441
|
-
app,
|
442
|
-
rescue_options: { backtrace: true, original_exception: true },
|
443
|
-
parsers: { json: ->(_object, _env) { raise StandardError, 'fail' } }
|
444
|
-
)
|
445
|
-
io = StringIO.new('{invalid}')
|
446
|
-
error = catch(:error) do
|
447
|
-
subject.call(
|
448
|
-
'PATH_INFO' => '/info',
|
449
|
-
'REQUEST_METHOD' => 'POST',
|
450
|
-
'CONTENT_TYPE' => 'application/json',
|
451
|
-
'rack.input' => io,
|
452
|
-
'CONTENT_LENGTH' => io.length
|
453
|
-
)
|
454
|
-
end
|
455
|
-
|
456
|
-
expect(error[:message]).to eq 'fail'
|
457
|
-
expect(error[:backtrace].size).to be >= 1
|
458
|
-
expect(error[:original_exception].class).to eq StandardError
|
459
|
-
end
|
460
|
-
end
|
461
|
-
end
|
@@ -1,30 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
describe Grape::Middleware::Globals do
|
4
|
-
subject { described_class.new(blank_app) }
|
5
|
-
|
6
|
-
before { allow(subject).to receive(:dup).and_return(subject) }
|
7
|
-
|
8
|
-
let(:blank_app) { ->(_env) { [200, {}, 'Hi there.'] } }
|
9
|
-
|
10
|
-
it 'calls through to the app' do
|
11
|
-
expect(subject.call({})).to eq([200, {}, 'Hi there.'])
|
12
|
-
end
|
13
|
-
|
14
|
-
context 'environment' do
|
15
|
-
it 'sets the grape.request environment' do
|
16
|
-
subject.call({})
|
17
|
-
expect(subject.env['grape.request']).to be_a(Grape::Request)
|
18
|
-
end
|
19
|
-
|
20
|
-
it 'sets the grape.request.headers environment' do
|
21
|
-
subject.call({})
|
22
|
-
expect(subject.env['grape.request.headers']).to be_a(Hash)
|
23
|
-
end
|
24
|
-
|
25
|
-
it 'sets the grape.request.params environment' do
|
26
|
-
subject.call('QUERY_STRING' => 'test=1', 'rack.input' => StringIO.new)
|
27
|
-
expect(subject.env['grape.request.params']).to be_a(Hash)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
@@ -1,155 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
describe Grape::Middleware::Stack do
|
4
|
-
module StackSpec
|
5
|
-
class FooMiddleware; end
|
6
|
-
|
7
|
-
class BarMiddleware; end
|
8
|
-
|
9
|
-
class BlockMiddleware
|
10
|
-
attr_reader :block
|
11
|
-
|
12
|
-
def initialize(&block)
|
13
|
-
@block = block
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
subject { described_class.new }
|
19
|
-
|
20
|
-
let(:proc) { -> {} }
|
21
|
-
let(:others) { [[:use, StackSpec::BarMiddleware], [:insert_before, StackSpec::BarMiddleware, StackSpec::BlockMiddleware, proc]] }
|
22
|
-
|
23
|
-
before do
|
24
|
-
subject.use StackSpec::FooMiddleware
|
25
|
-
end
|
26
|
-
|
27
|
-
describe '#use' do
|
28
|
-
it 'pushes a middleware class onto the stack' do
|
29
|
-
expect { subject.use StackSpec::BarMiddleware }
|
30
|
-
.to change(subject, :size).by(1)
|
31
|
-
expect(subject.last).to eq(StackSpec::BarMiddleware)
|
32
|
-
end
|
33
|
-
|
34
|
-
it 'pushes a middleware class with arguments onto the stack' do
|
35
|
-
expect { subject.use StackSpec::BarMiddleware, false, my_arg: 42 }
|
36
|
-
.to change(subject, :size).by(1)
|
37
|
-
expect(subject.last).to eq(StackSpec::BarMiddleware)
|
38
|
-
expect(subject.last.args).to eq([false, { my_arg: 42 }])
|
39
|
-
end
|
40
|
-
|
41
|
-
it 'pushes a middleware class with block arguments onto the stack' do
|
42
|
-
expect { subject.use StackSpec::BlockMiddleware, &proc }
|
43
|
-
.to change(subject, :size).by(1)
|
44
|
-
expect(subject.last).to eq(StackSpec::BlockMiddleware)
|
45
|
-
expect(subject.last.args).to eq([])
|
46
|
-
expect(subject.last.block).to eq(proc)
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
describe '#insert' do
|
51
|
-
it 'inserts a middleware class at the integer index' do
|
52
|
-
expect { subject.insert 0, StackSpec::BarMiddleware }
|
53
|
-
.to change(subject, :size).by(1)
|
54
|
-
expect(subject[0]).to eq(StackSpec::BarMiddleware)
|
55
|
-
expect(subject[1]).to eq(StackSpec::FooMiddleware)
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
describe '#insert_before' do
|
60
|
-
it 'inserts a middleware before another middleware class' do
|
61
|
-
expect { subject.insert_before StackSpec::FooMiddleware, StackSpec::BarMiddleware }
|
62
|
-
.to change(subject, :size).by(1)
|
63
|
-
expect(subject[0]).to eq(StackSpec::BarMiddleware)
|
64
|
-
expect(subject[1]).to eq(StackSpec::FooMiddleware)
|
65
|
-
end
|
66
|
-
|
67
|
-
it 'inserts a middleware before an anonymous class given by its superclass' do
|
68
|
-
subject.use Class.new(StackSpec::BlockMiddleware)
|
69
|
-
|
70
|
-
expect { subject.insert_before StackSpec::BlockMiddleware, StackSpec::BarMiddleware }
|
71
|
-
.to change(subject, :size).by(1)
|
72
|
-
|
73
|
-
expect(subject[1]).to eq(StackSpec::BarMiddleware)
|
74
|
-
expect(subject[2]).to eq(StackSpec::BlockMiddleware)
|
75
|
-
end
|
76
|
-
|
77
|
-
it 'raises an error on an invalid index' do
|
78
|
-
expect { subject.insert_before StackSpec::BlockMiddleware, StackSpec::BarMiddleware }
|
79
|
-
.to raise_error(RuntimeError, 'No such middleware to insert before: StackSpec::BlockMiddleware')
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
describe '#insert_after' do
|
84
|
-
it 'inserts a middleware after another middleware class' do
|
85
|
-
expect { subject.insert_after StackSpec::FooMiddleware, StackSpec::BarMiddleware }
|
86
|
-
.to change(subject, :size).by(1)
|
87
|
-
expect(subject[1]).to eq(StackSpec::BarMiddleware)
|
88
|
-
expect(subject[0]).to eq(StackSpec::FooMiddleware)
|
89
|
-
end
|
90
|
-
|
91
|
-
it 'inserts a middleware after an anonymous class given by its superclass' do
|
92
|
-
subject.use Class.new(StackSpec::BlockMiddleware)
|
93
|
-
|
94
|
-
expect { subject.insert_after StackSpec::BlockMiddleware, StackSpec::BarMiddleware }
|
95
|
-
.to change(subject, :size).by(1)
|
96
|
-
|
97
|
-
expect(subject[1]).to eq(StackSpec::BlockMiddleware)
|
98
|
-
expect(subject[2]).to eq(StackSpec::BarMiddleware)
|
99
|
-
end
|
100
|
-
|
101
|
-
it 'raises an error on an invalid index' do
|
102
|
-
expect { subject.insert_after StackSpec::BlockMiddleware, StackSpec::BarMiddleware }
|
103
|
-
.to raise_error(RuntimeError, 'No such middleware to insert after: StackSpec::BlockMiddleware')
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
describe '#merge_with' do
|
108
|
-
it 'applies a collection of operations and middlewares' do
|
109
|
-
expect { subject.merge_with(others) }
|
110
|
-
.to change(subject, :size).by(2)
|
111
|
-
expect(subject[0]).to eq(StackSpec::FooMiddleware)
|
112
|
-
expect(subject[1]).to eq(StackSpec::BlockMiddleware)
|
113
|
-
expect(subject[2]).to eq(StackSpec::BarMiddleware)
|
114
|
-
end
|
115
|
-
|
116
|
-
context 'middleware spec with proc declaration exists' do
|
117
|
-
let(:middleware_spec_with_proc) { [:use, StackSpec::FooMiddleware, proc] }
|
118
|
-
|
119
|
-
it 'properly forwards spec arguments' do
|
120
|
-
expect(subject).to receive(:use).with(StackSpec::FooMiddleware)
|
121
|
-
subject.merge_with([middleware_spec_with_proc])
|
122
|
-
end
|
123
|
-
end
|
124
|
-
end
|
125
|
-
|
126
|
-
describe '#build' do
|
127
|
-
it 'returns a rack builder instance' do
|
128
|
-
expect(subject.build).to be_instance_of(Rack::Builder)
|
129
|
-
end
|
130
|
-
|
131
|
-
context 'when @others are present' do
|
132
|
-
let(:others) { [[:insert_after, Grape::Middleware::Formatter, StackSpec::BarMiddleware]] }
|
133
|
-
|
134
|
-
it 'applies the middleware specs stored in @others' do
|
135
|
-
subject.concat others
|
136
|
-
subject.use Grape::Middleware::Formatter
|
137
|
-
subject.build
|
138
|
-
expect(subject[0]).to eq StackSpec::FooMiddleware
|
139
|
-
expect(subject[1]).to eq Grape::Middleware::Formatter
|
140
|
-
expect(subject[2]).to eq StackSpec::BarMiddleware
|
141
|
-
end
|
142
|
-
end
|
143
|
-
end
|
144
|
-
|
145
|
-
describe '#concat' do
|
146
|
-
it 'adds non :use specs to @others' do
|
147
|
-
expect { subject.concat others }.to change(subject, :others).from([]).to([[others.last]])
|
148
|
-
end
|
149
|
-
|
150
|
-
it 'calls +merge_with+ with the :use specs' do
|
151
|
-
expect(subject).to receive(:merge_with).with [[:use, StackSpec::BarMiddleware]]
|
152
|
-
subject.concat others
|
153
|
-
end
|
154
|
-
end
|
155
|
-
end
|