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