grape 1.7.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (163) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +37 -1
  3. data/CONTRIBUTING.md +1 -1
  4. data/README.md +30 -25
  5. data/UPGRADING.md +35 -0
  6. data/grape.gemspec +3 -6
  7. data/lib/grape/api.rb +2 -2
  8. data/lib/grape/content_types.rb +2 -8
  9. data/lib/grape/dsl/desc.rb +1 -1
  10. data/lib/grape/dsl/inside_route.rb +11 -11
  11. data/lib/grape/dsl/request_response.rb +2 -1
  12. data/lib/grape/dsl/settings.rb +2 -6
  13. data/lib/grape/endpoint.rb +28 -18
  14. data/lib/grape/error_formatter/base.rb +1 -1
  15. data/lib/grape/exceptions/base.rb +2 -2
  16. data/lib/grape/exceptions/missing_group_type.rb +1 -6
  17. data/lib/grape/exceptions/unsupported_group_type.rb +1 -6
  18. data/lib/grape/exceptions/validation_errors.rb +1 -6
  19. data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +3 -3
  20. data/lib/grape/extensions/hash.rb +4 -7
  21. data/lib/grape/extensions/hashie/mash.rb +3 -3
  22. data/lib/grape/formatter/serializable_hash.rb +7 -7
  23. data/lib/grape/http/headers.rb +12 -2
  24. data/lib/grape/middleware/auth/base.rb +1 -1
  25. data/lib/grape/middleware/auth/strategies.rb +1 -2
  26. data/lib/grape/middleware/error.rb +5 -5
  27. data/lib/grape/middleware/formatter.rb +6 -6
  28. data/lib/grape/middleware/versioner/header.rb +11 -19
  29. data/lib/grape/railtie.rb +9 -0
  30. data/lib/grape/request.rb +8 -2
  31. data/lib/grape/router/route.rb +1 -3
  32. data/lib/grape/util/lazy_value.rb +3 -11
  33. data/lib/grape/util/strict_hash_configuration.rb +3 -4
  34. data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
  35. data/lib/grape/validations/params_scope.rb +8 -2
  36. data/lib/grape/validations/single_attribute_iterator.rb +3 -1
  37. data/lib/grape/validations/types/custom_type_coercer.rb +2 -16
  38. data/lib/grape/validations/validators/base.rb +9 -20
  39. data/lib/grape/validations/validators/default_validator.rb +2 -20
  40. data/lib/grape/validations/validators/multiple_params_base.rb +4 -8
  41. data/lib/grape/validations/validators/values_validator.rb +14 -5
  42. data/lib/grape/version.rb +1 -1
  43. data/lib/grape.rb +26 -5
  44. metadata +13 -253
  45. data/lib/grape/config.rb +0 -34
  46. data/lib/grape/extensions/deep_mergeable_hash.rb +0 -21
  47. data/lib/grape/extensions/deep_symbolize_hash.rb +0 -32
  48. data/spec/grape/api/custom_validations_spec.rb +0 -256
  49. data/spec/grape/api/deeply_included_options_spec.rb +0 -56
  50. data/spec/grape/api/defines_boolean_in_params_spec.rb +0 -38
  51. data/spec/grape/api/documentation_spec.rb +0 -59
  52. data/spec/grape/api/inherited_helpers_spec.rb +0 -114
  53. data/spec/grape/api/instance_spec.rb +0 -103
  54. data/spec/grape/api/invalid_format_spec.rb +0 -45
  55. data/spec/grape/api/namespace_parameters_in_route_spec.rb +0 -38
  56. data/spec/grape/api/nested_helpers_spec.rb +0 -50
  57. data/spec/grape/api/optional_parameters_in_route_spec.rb +0 -43
  58. data/spec/grape/api/parameters_modification_spec.rb +0 -41
  59. data/spec/grape/api/patch_method_helpers_spec.rb +0 -79
  60. data/spec/grape/api/recognize_path_spec.rb +0 -21
  61. data/spec/grape/api/required_parameters_in_route_spec.rb +0 -37
  62. data/spec/grape/api/required_parameters_with_invalid_method_spec.rb +0 -26
  63. data/spec/grape/api/routes_with_requirements_spec.rb +0 -59
  64. data/spec/grape/api/shared_helpers_exactly_one_of_spec.rb +0 -41
  65. data/spec/grape/api/shared_helpers_spec.rb +0 -36
  66. data/spec/grape/api_remount_spec.rb +0 -473
  67. data/spec/grape/api_spec.rb +0 -4347
  68. data/spec/grape/config_spec.rb +0 -17
  69. data/spec/grape/dsl/callbacks_spec.rb +0 -45
  70. data/spec/grape/dsl/desc_spec.rb +0 -101
  71. data/spec/grape/dsl/headers_spec.rb +0 -62
  72. data/spec/grape/dsl/helpers_spec.rb +0 -100
  73. data/spec/grape/dsl/inside_route_spec.rb +0 -535
  74. data/spec/grape/dsl/logger_spec.rb +0 -24
  75. data/spec/grape/dsl/middleware_spec.rb +0 -60
  76. data/spec/grape/dsl/parameters_spec.rb +0 -180
  77. data/spec/grape/dsl/request_response_spec.rb +0 -206
  78. data/spec/grape/dsl/routing_spec.rb +0 -275
  79. data/spec/grape/dsl/settings_spec.rb +0 -261
  80. data/spec/grape/dsl/validations_spec.rb +0 -55
  81. data/spec/grape/endpoint/declared_spec.rb +0 -846
  82. data/spec/grape/endpoint_spec.rb +0 -1085
  83. data/spec/grape/entity_spec.rb +0 -336
  84. data/spec/grape/exceptions/base_spec.rb +0 -81
  85. data/spec/grape/exceptions/body_parse_errors_spec.rb +0 -145
  86. data/spec/grape/exceptions/invalid_accept_header_spec.rb +0 -358
  87. data/spec/grape/exceptions/invalid_formatter_spec.rb +0 -15
  88. data/spec/grape/exceptions/invalid_response_spec.rb +0 -11
  89. data/spec/grape/exceptions/invalid_versioner_option_spec.rb +0 -15
  90. data/spec/grape/exceptions/missing_group_type_spec.rb +0 -21
  91. data/spec/grape/exceptions/missing_mime_type_spec.rb +0 -17
  92. data/spec/grape/exceptions/missing_option_spec.rb +0 -15
  93. data/spec/grape/exceptions/unknown_options_spec.rb +0 -15
  94. data/spec/grape/exceptions/unknown_validator_spec.rb +0 -15
  95. data/spec/grape/exceptions/unsupported_group_type_spec.rb +0 -23
  96. data/spec/grape/exceptions/validation_errors_spec.rb +0 -92
  97. data/spec/grape/exceptions/validation_spec.rb +0 -19
  98. data/spec/grape/extensions/param_builders/hash_spec.rb +0 -83
  99. data/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb +0 -105
  100. data/spec/grape/extensions/param_builders/hashie/mash_spec.rb +0 -79
  101. data/spec/grape/integration/global_namespace_function_spec.rb +0 -29
  102. data/spec/grape/integration/rack_sendfile_spec.rb +0 -48
  103. data/spec/grape/integration/rack_spec.rb +0 -51
  104. data/spec/grape/loading_spec.rb +0 -44
  105. data/spec/grape/middleware/auth/base_spec.rb +0 -31
  106. data/spec/grape/middleware/auth/dsl_spec.rb +0 -60
  107. data/spec/grape/middleware/auth/strategies_spec.rb +0 -120
  108. data/spec/grape/middleware/base_spec.rb +0 -221
  109. data/spec/grape/middleware/error_spec.rb +0 -85
  110. data/spec/grape/middleware/exception_spec.rb +0 -294
  111. data/spec/grape/middleware/formatter_spec.rb +0 -461
  112. data/spec/grape/middleware/globals_spec.rb +0 -30
  113. data/spec/grape/middleware/stack_spec.rb +0 -155
  114. data/spec/grape/middleware/versioner/accept_version_header_spec.rb +0 -122
  115. data/spec/grape/middleware/versioner/header_spec.rb +0 -345
  116. data/spec/grape/middleware/versioner/param_spec.rb +0 -171
  117. data/spec/grape/middleware/versioner/path_spec.rb +0 -62
  118. data/spec/grape/middleware/versioner_spec.rb +0 -21
  119. data/spec/grape/named_api_spec.rb +0 -19
  120. data/spec/grape/parser_spec.rb +0 -86
  121. data/spec/grape/path_spec.rb +0 -252
  122. data/spec/grape/presenters/presenter_spec.rb +0 -71
  123. data/spec/grape/request_spec.rb +0 -136
  124. data/spec/grape/util/inheritable_setting_spec.rb +0 -242
  125. data/spec/grape/util/inheritable_values_spec.rb +0 -79
  126. data/spec/grape/util/reverse_stackable_values_spec.rb +0 -134
  127. data/spec/grape/util/stackable_values_spec.rb +0 -128
  128. data/spec/grape/util/strict_hash_configuration_spec.rb +0 -38
  129. data/spec/grape/validations/attributes_doc_spec.rb +0 -153
  130. data/spec/grape/validations/instance_behaivour_spec.rb +0 -43
  131. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +0 -40
  132. data/spec/grape/validations/params_scope_spec.rb +0 -1420
  133. data/spec/grape/validations/single_attribute_iterator_spec.rb +0 -57
  134. data/spec/grape/validations/types/array_coercer_spec.rb +0 -33
  135. data/spec/grape/validations/types/primitive_coercer_spec.rb +0 -150
  136. data/spec/grape/validations/types/set_coercer_spec.rb +0 -32
  137. data/spec/grape/validations/types_spec.rb +0 -111
  138. data/spec/grape/validations/validators/all_or_none_spec.rb +0 -162
  139. data/spec/grape/validations/validators/allow_blank_spec.rb +0 -575
  140. data/spec/grape/validations/validators/at_least_one_of_spec.rb +0 -205
  141. data/spec/grape/validations/validators/coerce_spec.rb +0 -1261
  142. data/spec/grape/validations/validators/default_spec.rb +0 -463
  143. data/spec/grape/validations/validators/exactly_one_of_spec.rb +0 -233
  144. data/spec/grape/validations/validators/except_values_spec.rb +0 -192
  145. data/spec/grape/validations/validators/mutual_exclusion_spec.rb +0 -214
  146. data/spec/grape/validations/validators/presence_spec.rb +0 -315
  147. data/spec/grape/validations/validators/regexp_spec.rb +0 -161
  148. data/spec/grape/validations/validators/same_as_spec.rb +0 -57
  149. data/spec/grape/validations/validators/values_spec.rb +0 -696
  150. data/spec/grape/validations/validators/zh-CN.yml +0 -10
  151. data/spec/grape/validations_spec.rb +0 -2029
  152. data/spec/integration/eager_load/eager_load_spec.rb +0 -15
  153. data/spec/integration/multi_json/json_spec.rb +0 -7
  154. data/spec/integration/multi_xml/xml_spec.rb +0 -7
  155. data/spec/shared/versioning_examples.rb +0 -215
  156. data/spec/spec_helper.rb +0 -52
  157. data/spec/support/basic_auth_encode_helpers.rb +0 -11
  158. data/spec/support/chunks.rb +0 -14
  159. data/spec/support/content_type_helpers.rb +0 -15
  160. data/spec/support/endpoint_faker.rb +0 -25
  161. data/spec/support/file_streamer.rb +0 -13
  162. data/spec/support/integer_helpers.rb +0 -13
  163. 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).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