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,1085 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- describe Grape::Endpoint do
4
- subject { Class.new(Grape::API) }
5
-
6
- def app
7
- subject
8
- end
9
-
10
- describe '.before_each' do
11
- after { described_class.before_each.clear }
12
-
13
- it 'is settable via block' do
14
- block = ->(_endpoint) { 'noop' }
15
- described_class.before_each(&block)
16
- expect(described_class.before_each.first).to eq(block)
17
- end
18
-
19
- it 'is settable via reference' do
20
- block = ->(_endpoint) { 'noop' }
21
- described_class.before_each block
22
- expect(described_class.before_each.first).to eq(block)
23
- end
24
-
25
- it 'is able to override a helper' do
26
- subject.get('/') { current_user }
27
- expect { get '/' }.to raise_error(NameError)
28
-
29
- described_class.before_each do |endpoint|
30
- allow(endpoint).to receive(:current_user).and_return('Bob')
31
- end
32
-
33
- get '/'
34
- expect(last_response.body).to eq('Bob')
35
-
36
- described_class.before_each(nil)
37
- expect { get '/' }.to raise_error(NameError)
38
- end
39
-
40
- it 'is able to stack helper' do
41
- subject.get('/') do
42
- authenticate_user!
43
- current_user
44
- end
45
- expect { get '/' }.to raise_error(NameError)
46
-
47
- described_class.before_each do |endpoint|
48
- allow(endpoint).to receive(:current_user).and_return('Bob')
49
- end
50
-
51
- described_class.before_each do |endpoint|
52
- allow(endpoint).to receive(:authenticate_user!).and_return(true)
53
- end
54
-
55
- get '/'
56
- expect(last_response.body).to eq('Bob')
57
-
58
- described_class.before_each(nil)
59
- expect { get '/' }.to raise_error(NameError)
60
- end
61
- end
62
-
63
- describe '#initialize' do
64
- it 'takes a settings stack, options, and a block' do
65
- p = proc {}
66
- expect do
67
- described_class.new(Grape::Util::InheritableSetting.new, {
68
- path: '/',
69
- method: :get
70
- }, &p)
71
- end.not_to raise_error
72
- end
73
- end
74
-
75
- it 'sets itself in the env upon call' do
76
- subject.get('/') { 'Hello world.' }
77
- get '/'
78
- expect(last_request.env['api.endpoint']).to be_a(described_class)
79
- end
80
-
81
- describe '#status' do
82
- it 'is callable from within a block' do
83
- subject.get('/home') do
84
- status 206
85
- 'Hello'
86
- end
87
-
88
- get '/home'
89
- expect(last_response.status).to eq(206)
90
- expect(last_response.body).to eq('Hello')
91
- end
92
-
93
- it 'is set as default to 200 for get' do
94
- memoized_status = nil
95
- subject.get('/home') do
96
- memoized_status = status
97
- 'Hello'
98
- end
99
-
100
- get '/home'
101
- expect(last_response.status).to eq(200)
102
- expect(memoized_status).to eq(200)
103
- expect(last_response.body).to eq('Hello')
104
- end
105
-
106
- it 'is set as default to 201 for post' do
107
- memoized_status = nil
108
- subject.post('/home') do
109
- memoized_status = status
110
- 'Hello'
111
- end
112
-
113
- post '/home'
114
- expect(last_response.status).to eq(201)
115
- expect(memoized_status).to eq(201)
116
- expect(last_response.body).to eq('Hello')
117
- end
118
- end
119
-
120
- describe '#header' do
121
- it 'is callable from within a block' do
122
- subject.get('/hey') do
123
- header 'X-Awesome', 'true'
124
- 'Awesome'
125
- end
126
-
127
- get '/hey'
128
- expect(last_response.headers['X-Awesome']).to eq('true')
129
- end
130
- end
131
-
132
- describe '#headers' do
133
- before do
134
- subject.get('/headers') do
135
- headers.to_json
136
- end
137
- end
138
-
139
- it 'includes request headers' do
140
- get '/headers'
141
- expect(JSON.parse(last_response.body)).to include(
142
- 'Host' => 'example.org',
143
- 'Cookie' => ''
144
- )
145
- end
146
-
147
- it 'includes additional request headers' do
148
- get '/headers', nil, 'HTTP_X_GRAPE_CLIENT' => '1'
149
- expect(JSON.parse(last_response.body)['X-Grape-Client']).to eq('1')
150
- end
151
-
152
- it 'includes headers passed as symbols' do
153
- env = Rack::MockRequest.env_for('/headers')
154
- env[:HTTP_SYMBOL_HEADER] = 'Goliath passes symbols'
155
- body = read_chunks(subject.call(env)[2]).join
156
- expect(JSON.parse(body)['Symbol-Header']).to eq('Goliath passes symbols')
157
- end
158
- end
159
-
160
- describe '#cookies' do
161
- it 'is callable from within a block' do
162
- subject.get('/get/cookies') do
163
- cookies['my-awesome-cookie1'] = 'is cool'
164
- cookies['my-awesome-cookie2'] = {
165
- value: 'is cool too',
166
- domain: 'my.example.com',
167
- path: '/',
168
- secure: true
169
- }
170
- cookies[:cookie3] = 'symbol'
171
- cookies['cookie4'] = 'secret code here'
172
- end
173
-
174
- get('/get/cookies')
175
-
176
- expect(Array(last_response.headers['Set-Cookie']).flat_map { |h| h.split("\n") }.sort).to eql [
177
- 'cookie3=symbol',
178
- 'cookie4=secret+code+here',
179
- 'my-awesome-cookie1=is+cool',
180
- 'my-awesome-cookie2=is+cool+too; domain=my.example.com; path=/; secure'
181
- ]
182
- end
183
-
184
- it 'sets browser cookies and does not set response cookies' do
185
- subject.get('/username') do
186
- cookies[:username]
187
- end
188
- get('/username', {}, 'HTTP_COOKIE' => 'username=mrplum; sandbox=true')
189
-
190
- expect(last_response.body).to eq('mrplum')
191
- expect(last_response.headers['Set-Cookie']).to be_nil
192
- end
193
-
194
- it 'sets and update browser cookies' do
195
- subject.get('/username') do
196
- cookies[:sandbox] = true if cookies[:sandbox] == 'false'
197
- cookies[:username] += '_test'
198
- end
199
- get('/username', {}, 'HTTP_COOKIE' => 'username=user; sandbox=false')
200
- expect(last_response.body).to eq('user_test')
201
- cookies = Array(last_response.headers['Set-Cookie']).flat_map { |h| h.split("\n") }
202
- expect(cookies.first).to match(/username=user_test/)
203
- expect(cookies.second).to match(/sandbox=true/)
204
- end
205
-
206
- it 'deletes cookie' do
207
- subject.get('/test') do
208
- sum = 0
209
- cookies.each do |name, val|
210
- sum += val.to_i
211
- cookies.delete name
212
- end
213
- sum
214
- end
215
- get '/test', {}, 'HTTP_COOKIE' => 'delete_this_cookie=1; and_this=2'
216
- expect(last_response.body).to eq('3')
217
- cookies = Array(last_response.headers['Set-Cookie']).flat_map { |h| h.split("\n") }.to_h do |set_cookie|
218
- cookie = CookieJar::Cookie.from_set_cookie 'http://localhost/test', set_cookie
219
- [cookie.name, cookie]
220
- end
221
- expect(cookies.size).to eq(2)
222
- %w[and_this delete_this_cookie].each do |cookie_name|
223
- cookie = cookies[cookie_name]
224
- expect(cookie).not_to be_nil
225
- expect(cookie.value).to eq('deleted')
226
- expect(cookie.expired?).to be true
227
- end
228
- end
229
-
230
- it 'deletes cookies with path' do
231
- subject.get('/test') do
232
- sum = 0
233
- cookies.each do |name, val|
234
- sum += val.to_i
235
- cookies.delete name, path: '/test'
236
- end
237
- sum
238
- end
239
- get('/test', {}, 'HTTP_COOKIE' => 'delete_this_cookie=1; and_this=2')
240
- expect(last_response.body).to eq('3')
241
- cookies = Array(last_response.headers['Set-Cookie']).flat_map { |h| h.split("\n") }.to_h do |set_cookie|
242
- cookie = CookieJar::Cookie.from_set_cookie 'http://localhost/test', set_cookie
243
- [cookie.name, cookie]
244
- end
245
- expect(cookies.size).to eq(2)
246
- %w[and_this delete_this_cookie].each do |cookie_name|
247
- cookie = cookies[cookie_name]
248
- expect(cookie).not_to be_nil
249
- expect(cookie.value).to eq('deleted')
250
- expect(cookie.path).to eq('/test')
251
- expect(cookie.expired?).to be true
252
- end
253
- end
254
- end
255
-
256
- describe '#params' do
257
- context 'default class' do
258
- it 'is a ActiveSupport::HashWithIndifferentAccess' do
259
- subject.get '/foo' do
260
- params.class
261
- end
262
-
263
- get '/foo'
264
- expect(last_response.status).to eq(200)
265
- expect(last_response.body).to eq('ActiveSupport::HashWithIndifferentAccess')
266
- end
267
- end
268
-
269
- context 'sets a value to params' do
270
- it 'params' do
271
- subject.params do
272
- requires :a, type: String
273
- end
274
- subject.get '/foo' do
275
- params[:a] = 'bar'
276
- end
277
-
278
- get '/foo', a: 'foo'
279
- expect(last_response.status).to eq(200)
280
- expect(last_response.body).to eq('bar')
281
- end
282
- end
283
- end
284
-
285
- describe '#params' do
286
- it 'is available to the caller' do
287
- subject.get('/hey') do
288
- params[:howdy]
289
- end
290
-
291
- get '/hey?howdy=hey'
292
- expect(last_response.body).to eq('hey')
293
- end
294
-
295
- it 'parses from path segments' do
296
- subject.get('/hey/:id') do
297
- params[:id]
298
- end
299
-
300
- get '/hey/12'
301
- expect(last_response.body).to eq('12')
302
- end
303
-
304
- it 'deeply converts nested params' do
305
- subject.get '/location' do
306
- params[:location][:city]
307
- end
308
- get '/location?location[city]=Dallas'
309
- expect(last_response.body).to eq('Dallas')
310
- end
311
-
312
- context 'with special requirements' do
313
- it 'parses email param with provided requirements for params' do
314
- subject.get('/:person_email', requirements: { person_email: /.*/ }) do
315
- params[:person_email]
316
- end
317
-
318
- get '/someone@example.com'
319
- expect(last_response.body).to eq('someone@example.com')
320
-
321
- get 'someone@example.com.pl'
322
- expect(last_response.body).to eq('someone@example.com.pl')
323
- end
324
-
325
- it 'parses many params with provided regexps' do
326
- subject.get('/:person_email/test/:number', requirements: { person_email: /someone@(.*).com/, number: /[0-9]/ }) do
327
- params[:person_email] << params[:number]
328
- end
329
-
330
- get '/someone@example.com/test/1'
331
- expect(last_response.body).to eq('someone@example.com1')
332
-
333
- get '/someone@testing.wrong/test/1'
334
- expect(last_response.status).to eq(404)
335
-
336
- get 'someone@test.com/test/wrong_number'
337
- expect(last_response.status).to eq(404)
338
-
339
- get 'someone@test.com/wrong_middle/1'
340
- expect(last_response.status).to eq(404)
341
- end
342
-
343
- context 'namespace requirements' do
344
- before do
345
- subject.namespace :outer, requirements: { person_email: /abc@(.*).com/ } do
346
- get('/:person_email') do
347
- params[:person_email]
348
- end
349
-
350
- namespace :inner, requirements: { number: /[0-9]/, person_email: /someone@(.*).com/ } do
351
- get '/:person_email/test/:number' do
352
- params[:person_email] << params[:number]
353
- end
354
- end
355
- end
356
- end
357
-
358
- it 'parse email param with provided requirements for params' do
359
- get '/outer/abc@example.com'
360
- expect(last_response.body).to eq('abc@example.com')
361
- end
362
-
363
- it "overrides outer namespace's requirements" do
364
- get '/outer/inner/someone@testing.wrong/test/1'
365
- expect(last_response.status).to eq(404)
366
-
367
- get '/outer/inner/someone@testing.com/test/1'
368
- expect(last_response.status).to eq(200)
369
- expect(last_response.body).to eq('someone@testing.com1')
370
- end
371
- end
372
- end
373
-
374
- context 'from body parameters' do
375
- before do
376
- subject.post '/request_body' do
377
- params[:user]
378
- end
379
- subject.put '/request_body' do
380
- params[:user]
381
- end
382
- end
383
-
384
- it 'converts JSON bodies to params' do
385
- post '/request_body', ::Grape::Json.dump(user: 'Bobby T.'), 'CONTENT_TYPE' => 'application/json'
386
- expect(last_response.body).to eq('Bobby T.')
387
- end
388
-
389
- it 'does not convert empty JSON bodies to params' do
390
- put '/request_body', '', 'CONTENT_TYPE' => 'application/json'
391
- expect(last_response.body).to eq('')
392
- end
393
-
394
- if Object.const_defined? :MultiXml
395
- it 'converts XML bodies to params' do
396
- post '/request_body', '<user>Bobby T.</user>', 'CONTENT_TYPE' => 'application/xml'
397
- expect(last_response.body).to eq('Bobby T.')
398
- end
399
-
400
- it 'converts XML bodies to params' do
401
- put '/request_body', '<user>Bobby T.</user>', 'CONTENT_TYPE' => 'application/xml'
402
- expect(last_response.body).to eq('Bobby T.')
403
- end
404
- else
405
- it 'converts XML bodies to params' do
406
- post '/request_body', '<user>Bobby T.</user>', 'CONTENT_TYPE' => 'application/xml'
407
- expect(last_response.body).to eq('{"__content__"=>"Bobby T."}')
408
- end
409
-
410
- it 'converts XML bodies to params' do
411
- put '/request_body', '<user>Bobby T.</user>', 'CONTENT_TYPE' => 'application/xml'
412
- expect(last_response.body).to eq('{"__content__"=>"Bobby T."}')
413
- end
414
- end
415
-
416
- it 'does not include parameters not defined by the body' do
417
- subject.post '/omitted_params' do
418
- error! 400, 'expected nil' if params[:version]
419
- params[:user]
420
- end
421
- post '/omitted_params', ::Grape::Json.dump(user: 'Bob'), 'CONTENT_TYPE' => 'application/json'
422
- expect(last_response.status).to eq(201)
423
- expect(last_response.body).to eq('Bob')
424
- end
425
-
426
- # Rack swallowed this error until v2.2.0
427
- it 'returns a 400 if given an invalid multipart body', if: Gem::Version.new(Rack.release) >= Gem::Version.new('2.2.0') do
428
- subject.params do
429
- requires :file, type: Rack::Multipart::UploadedFile
430
- end
431
- subject.post '/upload' do
432
- params[:file][:filename]
433
- end
434
- post '/upload', { file: '' }, 'CONTENT_TYPE' => 'multipart/form-data; boundary=foobar'
435
- expect(last_response.status).to eq(400)
436
- expect(last_response.body).to eq('file is invalid')
437
- end
438
- end
439
-
440
- context 'when the limit on multipart files is exceeded' do
441
- around do |example|
442
- limit = Rack::Utils.multipart_part_limit
443
- Rack::Utils.multipart_part_limit = 1
444
- example.run
445
- Rack::Utils.multipart_part_limit = limit
446
- end
447
-
448
- it 'returns a 413 if given too many multipart files' do
449
- subject.params do
450
- requires :file, type: Rack::Multipart::UploadedFile
451
- end
452
- subject.post '/upload' do
453
- params[:file][:filename]
454
- end
455
- post '/upload', { file: Rack::Test::UploadedFile.new(__FILE__, 'text/plain'), extra: Rack::Test::UploadedFile.new(__FILE__, 'text/plain') }
456
- expect(last_response.status).to eq(413)
457
- expect(last_response.body).to eq("the number of uploaded files exceeded the system's configured limit (1)")
458
- end
459
- end
460
-
461
- it 'responds with a 415 for an unsupported content-type' do
462
- subject.format :json
463
- # subject.content_type :json, "application/json"
464
- subject.put '/request_body' do
465
- params[:user]
466
- end
467
- put '/request_body', '<user>Bobby T.</user>', 'CONTENT_TYPE' => 'application/xml'
468
- expect(last_response.status).to eq(415)
469
- expect(last_response.body).to eq('{"error":"The provided content-type \'application/xml\' is not supported."}')
470
- end
471
-
472
- it 'does not accept text/plain in JSON format if application/json is specified as content type' do
473
- subject.format :json
474
- subject.default_format :json
475
- subject.put '/request_body' do
476
- params[:user]
477
- end
478
- put '/request_body', ::Grape::Json.dump(user: 'Bob'), 'CONTENT_TYPE' => 'text/plain'
479
-
480
- expect(last_response.status).to eq(415)
481
- expect(last_response.body).to eq('{"error":"The provided content-type \'text/plain\' is not supported."}')
482
- end
483
-
484
- context 'content type with params' do
485
- before do
486
- subject.format :json
487
- subject.content_type :json, 'application/json; charset=utf-8'
488
-
489
- subject.post do
490
- params[:data]
491
- end
492
- post '/', ::Grape::Json.dump(data: { some: 'payload' }), 'CONTENT_TYPE' => 'application/json'
493
- end
494
-
495
- it 'does not response with 406 for same type without params' do
496
- expect(last_response.status).not_to be 406
497
- end
498
-
499
- it 'responses with given content type in headers' do
500
- expect(last_response.headers['Content-Type']).to eq 'application/json; charset=utf-8'
501
- end
502
- end
503
-
504
- context 'precedence' do
505
- before do
506
- subject.format :json
507
- subject.namespace '/:id' do
508
- get do
509
- {
510
- params: params[:id]
511
- }
512
- end
513
- post do
514
- {
515
- params: params[:id]
516
- }
517
- end
518
- put do
519
- {
520
- params: params[:id]
521
- }
522
- end
523
- end
524
- end
525
-
526
- it 'route string params have higher precedence than body params' do
527
- post '/123', { id: 456 }.to_json
528
- expect(JSON.parse(last_response.body)['params']).to eq '123'
529
- put '/123', { id: 456 }.to_json
530
- expect(JSON.parse(last_response.body)['params']).to eq '123'
531
- end
532
-
533
- it 'route string params have higher precedence than URL params' do
534
- get '/123?id=456'
535
- expect(JSON.parse(last_response.body)['params']).to eq '123'
536
- post '/123?id=456'
537
- expect(JSON.parse(last_response.body)['params']).to eq '123'
538
- end
539
- end
540
-
541
- context 'sets a value to params' do
542
- it 'params' do
543
- subject.params do
544
- requires :a, type: String
545
- end
546
- subject.get '/foo' do
547
- params[:a] = 'bar'
548
- end
549
-
550
- get '/foo', a: 'foo'
551
- expect(last_response.status).to eq(200)
552
- expect(last_response.body).to eq('bar')
553
- end
554
- end
555
- end
556
-
557
- describe '#error!' do
558
- it 'accepts a message' do
559
- subject.get('/hey') do
560
- error! 'This is not valid.'
561
- 'This is valid.'
562
- end
563
-
564
- get '/hey'
565
- expect(last_response.status).to eq(500)
566
- expect(last_response.body).to eq('This is not valid.')
567
- end
568
-
569
- it 'accepts a code' do
570
- subject.get('/hey') do
571
- error! 'Unauthorized.', 401
572
- end
573
-
574
- get '/hey'
575
- expect(last_response.status).to eq(401)
576
- expect(last_response.body).to eq('Unauthorized.')
577
- end
578
-
579
- it 'accepts an object and render it in format' do
580
- subject.get '/hey' do
581
- error!({ 'dude' => 'rad' }, 403)
582
- end
583
-
584
- get '/hey.json'
585
- expect(last_response.status).to eq(403)
586
- expect(last_response.body).to eq('{"dude":"rad"}')
587
- end
588
-
589
- it 'accepts a frozen object' do
590
- subject.get '/hey' do
591
- error!({ 'dude' => 'rad' }.freeze, 403)
592
- end
593
-
594
- get '/hey.json'
595
- expect(last_response.status).to eq(403)
596
- expect(last_response.body).to eq('{"dude":"rad"}')
597
- end
598
-
599
- it 'can specifiy headers' do
600
- subject.get '/hey' do
601
- error!({ 'dude' => 'rad' }, 403, 'X-Custom' => 'value')
602
- end
603
-
604
- get '/hey.json'
605
- expect(last_response.status).to eq(403)
606
- expect(last_response.headers['X-Custom']).to eq('value')
607
- end
608
-
609
- it 'merges additional headers with headers set before call' do
610
- subject.before do
611
- header 'X-Before-Test', 'before-sample'
612
- end
613
-
614
- subject.get '/hey' do
615
- header 'X-Test', 'test-sample'
616
- error!({ 'dude' => 'rad' }, 403, 'X-Error' => 'error')
617
- end
618
-
619
- get '/hey.json'
620
- expect(last_response.headers['X-Before-Test']).to eq('before-sample')
621
- expect(last_response.headers['X-Test']).to eq('test-sample')
622
- expect(last_response.headers['X-Error']).to eq('error')
623
- end
624
-
625
- it 'does not merges additional headers with headers set after call' do
626
- subject.after do
627
- header 'X-After-Test', 'after-sample'
628
- end
629
-
630
- subject.get '/hey' do
631
- error!({ 'dude' => 'rad' }, 403, 'X-Error' => 'error')
632
- end
633
-
634
- get '/hey.json'
635
- expect(last_response.headers['X-Error']).to eq('error')
636
- expect(last_response.headers['X-After-Test']).to be_nil
637
- end
638
-
639
- it 'sets the status code for the endpoint' do
640
- memoized_endpoint = nil
641
-
642
- subject.get '/hey' do
643
- memoized_endpoint = self
644
- error!({ 'dude' => 'rad' }, 403, 'X-Custom' => 'value')
645
- end
646
-
647
- get '/hey.json'
648
-
649
- expect(memoized_endpoint.status).to eq(403)
650
- end
651
- end
652
-
653
- describe '#redirect' do
654
- it 'redirects to a url with status 302' do
655
- subject.get('/hey') do
656
- redirect '/ha'
657
- end
658
- get '/hey'
659
- expect(last_response.status).to eq 302
660
- expect(last_response.headers['Location']).to eq '/ha'
661
- expect(last_response.body).to eq 'This resource has been moved temporarily to /ha.'
662
- end
663
-
664
- it 'has status code 303 if it is not get request and it is http 1.1' do
665
- subject.post('/hey') do
666
- redirect '/ha'
667
- end
668
- post '/hey', {}, 'HTTP_VERSION' => 'HTTP/1.1'
669
- expect(last_response.status).to eq 303
670
- expect(last_response.headers['Location']).to eq '/ha'
671
- expect(last_response.body).to eq 'An alternate resource is located at /ha.'
672
- end
673
-
674
- it 'support permanent redirect' do
675
- subject.get('/hey') do
676
- redirect '/ha', permanent: true
677
- end
678
- get '/hey'
679
- expect(last_response.status).to eq 301
680
- expect(last_response.headers['Location']).to eq '/ha'
681
- expect(last_response.body).to eq 'This resource has been moved permanently to /ha.'
682
- end
683
-
684
- it 'allows for an optional redirect body override' do
685
- subject.get('/hey') do
686
- redirect '/ha', body: 'test body'
687
- end
688
- get '/hey'
689
- expect(last_response.body).to eq 'test body'
690
- end
691
- end
692
-
693
- it 'does not persist params between calls' do
694
- subject.post('/new') do
695
- params[:text]
696
- end
697
-
698
- post '/new', text: 'abc'
699
- expect(last_response.body).to eq('abc')
700
-
701
- post '/new', text: 'def'
702
- expect(last_response.body).to eq('def')
703
- end
704
-
705
- it 'resets all instance variables (except block) between calls' do
706
- subject.helpers do
707
- def memoized
708
- @memoized ||= params[:howdy]
709
- end
710
- end
711
-
712
- subject.get('/hello') do
713
- memoized
714
- end
715
-
716
- get '/hello?howdy=hey'
717
- expect(last_response.body).to eq('hey')
718
- get '/hello?howdy=yo'
719
- expect(last_response.body).to eq('yo')
720
- end
721
-
722
- it 'allows explicit return calls' do
723
- subject.get('/home') do
724
- return 'Hello'
725
- end
726
-
727
- get '/home'
728
- expect(last_response.status).to eq(200)
729
- expect(last_response.body).to eq('Hello')
730
- end
731
-
732
- describe '.generate_api_method' do
733
- it 'raises NameError if the method name is already in use' do
734
- expect do
735
- described_class.generate_api_method('version', &proc {})
736
- end.to raise_error(NameError)
737
- end
738
-
739
- it 'raises ArgumentError if a block is not given' do
740
- expect do
741
- described_class.generate_api_method('GET without a block method')
742
- end.to raise_error(ArgumentError)
743
- end
744
-
745
- it 'returns a Proc' do
746
- expect(described_class.generate_api_method('GET test for a proc', &proc {})).to be_a Proc
747
- end
748
- end
749
-
750
- context 'filters' do
751
- describe 'before filters' do
752
- it 'runs the before filter if set' do
753
- subject.before { env['before_test'] = 'OK' }
754
- subject.get('/before_test') { env['before_test'] }
755
-
756
- get '/before_test'
757
- expect(last_response.body).to eq('OK')
758
- end
759
- end
760
-
761
- describe 'after filters' do
762
- it 'overrides the response body if it sets it' do
763
- subject.after { body 'after' }
764
- subject.get('/after_test') { 'during' }
765
- get '/after_test'
766
- expect(last_response.body).to eq('after')
767
- end
768
-
769
- it 'does not override the response body with its return' do
770
- subject.after { 'after' }
771
- subject.get('/after_test') { 'body' }
772
- get '/after_test'
773
- expect(last_response.body).to eq('body')
774
- end
775
- end
776
-
777
- it 'allows adding to response with present' do
778
- subject.format :json
779
- subject.before { present :before, 'before' }
780
- subject.before_validation { present :before_validation, 'before_validation' }
781
- subject.after_validation { present :after_validation, 'after_validation' }
782
- subject.after { present :after, 'after' }
783
- subject.get :all_filters do
784
- present :endpoint, 'endpoint'
785
- end
786
-
787
- get '/all_filters'
788
- json = JSON.parse(last_response.body)
789
- expect(json.keys).to match_array %w[before before_validation after_validation endpoint after]
790
- end
791
-
792
- context 'when terminating the response with error!' do
793
- it 'breaks normal call chain' do
794
- called = []
795
- subject.before { called << 'before' }
796
- subject.before_validation { called << 'before_validation' }
797
- subject.after_validation { error! :oops, 500 }
798
- subject.after { called << 'after' }
799
- subject.get :error_filters do
800
- called << 'endpoint'
801
- ''
802
- end
803
-
804
- get '/error_filters'
805
- expect(last_response.status).to be 500
806
- expect(called).to match_array %w[before before_validation]
807
- end
808
-
809
- it 'allows prior and parent filters of same type to run' do
810
- called = []
811
- subject.before { called << 'parent' }
812
- subject.namespace :parent do
813
- before { called << 'prior' }
814
-
815
- before { error! :oops, 500 }
816
-
817
- before { called << 'subsequent' }
818
-
819
- get :hello do
820
- called << :endpoint
821
- 'Hello!'
822
- end
823
- end
824
-
825
- get '/parent/hello'
826
- expect(last_response.status).to be 500
827
- expect(called).to match_array %w[parent prior]
828
- end
829
- end
830
- end
831
-
832
- context 'anchoring' do
833
- describe 'delete 204' do
834
- it 'allows for the anchoring option with a delete method' do
835
- subject.send(:delete, '/example', anchor: true) {}
836
- send(:delete, '/example/and/some/more')
837
- expect(last_response.status).to be 404
838
- end
839
-
840
- it 'anchors paths by default for the delete method' do
841
- subject.send(:delete, '/example') {}
842
- send(:delete, '/example/and/some/more')
843
- expect(last_response.status).to be 404
844
- end
845
-
846
- it 'responds to /example/and/some/more for the non-anchored delete method' do
847
- subject.send(:delete, '/example', anchor: false) {}
848
- send(:delete, '/example/and/some/more')
849
- expect(last_response.status).to be 204
850
- expect(last_response.body).to be_empty
851
- end
852
- end
853
-
854
- describe 'delete 200, with response body' do
855
- it 'responds to /example/and/some/more for the non-anchored delete method' do
856
- subject.send(:delete, '/example', anchor: false) do
857
- status 200
858
- body 'deleted'
859
- end
860
- send(:delete, '/example/and/some/more')
861
- expect(last_response.status).to be 200
862
- expect(last_response.body).not_to be_empty
863
- end
864
- end
865
-
866
- describe 'delete 200, with a return value (no explicit body)' do
867
- it 'responds to /example delete method' do
868
- subject.delete(:example) { 'deleted' }
869
- delete '/example'
870
- expect(last_response.status).to be 200
871
- expect(last_response.body).not_to be_empty
872
- end
873
- end
874
-
875
- describe 'delete 204, with nil has return value (no explicit body)' do
876
- it 'responds to /example delete method' do
877
- subject.delete(:example) { nil }
878
- delete '/example'
879
- expect(last_response.status).to be 204
880
- expect(last_response.body).to be_empty
881
- end
882
- end
883
-
884
- describe 'delete 204, with empty array has return value (no explicit body)' do
885
- it 'responds to /example delete method' do
886
- subject.delete(:example) { '' }
887
- delete '/example'
888
- expect(last_response.status).to be 204
889
- expect(last_response.body).to be_empty
890
- end
891
- end
892
-
893
- describe 'all other' do
894
- %w[post get head put options patch].each do |verb|
895
- it "allows for the anchoring option with a #{verb.upcase} method" do
896
- subject.send(verb, '/example', anchor: true) do
897
- verb
898
- end
899
- send(verb, '/example/and/some/more')
900
- expect(last_response.status).to be 404
901
- end
902
-
903
- it "anchors paths by default for the #{verb.upcase} method" do
904
- subject.send(verb, '/example') do
905
- verb
906
- end
907
- send(verb, '/example/and/some/more')
908
- expect(last_response.status).to be 404
909
- end
910
-
911
- it "responds to /example/and/some/more for the non-anchored #{verb.upcase} method" do
912
- subject.send(verb, '/example', anchor: false) do
913
- verb
914
- end
915
- send(verb, '/example/and/some/more')
916
- expect(last_response.status).to eql verb == 'post' ? 201 : 200
917
- expect(last_response.body).to eql verb == 'head' ? '' : verb
918
- end
919
- end
920
- end
921
- end
922
-
923
- context 'request' do
924
- it 'is set to the url requested' do
925
- subject.get('/url') do
926
- request.url
927
- end
928
- get '/url'
929
- expect(last_response.body).to eq('http://example.org/url')
930
- end
931
-
932
- ['v1', :v1].each do |version|
933
- it "includes version #{version}" do
934
- subject.version version, using: :path
935
- subject.get('/url') do
936
- request.url
937
- end
938
- get "/#{version}/url"
939
- expect(last_response.body).to eq("http://example.org/#{version}/url")
940
- end
941
- end
942
- it 'includes prefix' do
943
- subject.version 'v1', using: :path
944
- subject.prefix 'api'
945
- subject.get('/url') do
946
- request.url
947
- end
948
- get '/api/v1/url'
949
- expect(last_response.body).to eq('http://example.org/api/v1/url')
950
- end
951
- end
952
-
953
- context 'version headers' do
954
- before do
955
- # NOTE: a 404 is returned instead of the 406 if cascade: false is not set.
956
- subject.version 'v1', using: :header, vendor: 'ohanapi', cascade: false
957
- subject.get '/test' do
958
- 'Hello!'
959
- end
960
- end
961
-
962
- it 'result in a 406 response if they are invalid' do
963
- get '/test', {}, 'HTTP_ACCEPT' => 'application/vnd.ohanapi.v1+json'
964
- expect(last_response.status).to eq(406)
965
- end
966
-
967
- it 'result in a 406 response if they cannot be parsed by rack-accept' do
968
- get '/test', {}, 'HTTP_ACCEPT' => 'application/vnd.ohanapi.v1+json; version=1'
969
- expect(last_response.status).to eq(406)
970
- end
971
- end
972
-
973
- context 'binary' do
974
- before do
975
- subject.get do
976
- file FileStreamer.new(__FILE__)
977
- end
978
- end
979
-
980
- it 'suports stream objects in response' do
981
- get '/'
982
- expect(last_response.status).to eq 200
983
- expect(last_response.body).to eq File.read(__FILE__)
984
- end
985
- end
986
-
987
- context 'validation errors' do
988
- before do
989
- subject.before do
990
- header['Access-Control-Allow-Origin'] = '*'
991
- end
992
- subject.params do
993
- requires :id, type: String
994
- end
995
- subject.get do
996
- 'should not get here'
997
- end
998
- end
999
-
1000
- it 'returns the errors, and passes headers' do
1001
- get '/'
1002
- expect(last_response.status).to eq 400
1003
- expect(last_response.body).to eq 'id is missing'
1004
- expect(last_response.headers['Access-Control-Allow-Origin']).to eq('*')
1005
- end
1006
- end
1007
-
1008
- context 'instrumentation' do
1009
- before do
1010
- subject.before do
1011
- # Placeholder
1012
- end
1013
- subject.get do
1014
- 'hello'
1015
- end
1016
-
1017
- @events = []
1018
- @subscriber = ActiveSupport::Notifications.subscribe(/grape/) do |*args|
1019
- @events << ActiveSupport::Notifications::Event.new(*args)
1020
- end
1021
- end
1022
-
1023
- after do
1024
- ActiveSupport::Notifications.unsubscribe(@subscriber)
1025
- end
1026
-
1027
- it 'notifies AS::N' do
1028
- get '/'
1029
-
1030
- # In order that the events finalized (time each block ended)
1031
- expect(@events).to contain_exactly(
1032
- have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: a_kind_of(described_class),
1033
- filters: a_collection_containing_exactly(an_instance_of(Proc)),
1034
- type: :before }),
1035
- have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: a_kind_of(described_class),
1036
- filters: [],
1037
- type: :before_validation }),
1038
- have_attributes(name: 'endpoint_run_validators.grape', payload: { endpoint: a_kind_of(described_class),
1039
- validators: [],
1040
- request: a_kind_of(Grape::Request) }),
1041
- have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: a_kind_of(described_class),
1042
- filters: [],
1043
- type: :after_validation }),
1044
- have_attributes(name: 'endpoint_render.grape', payload: { endpoint: a_kind_of(described_class) }),
1045
- have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: a_kind_of(described_class),
1046
- filters: [],
1047
- type: :after }),
1048
- have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: a_kind_of(described_class),
1049
- filters: [],
1050
- type: :finally }),
1051
- have_attributes(name: 'endpoint_run.grape', payload: { endpoint: a_kind_of(described_class),
1052
- env: an_instance_of(Hash) }),
1053
- have_attributes(name: 'format_response.grape', payload: { env: an_instance_of(Hash),
1054
- formatter: a_kind_of(Module) })
1055
- )
1056
-
1057
- # In order that events were initialized
1058
- expect(@events.sort_by(&:time)).to contain_exactly(
1059
- have_attributes(name: 'endpoint_run.grape', payload: { endpoint: a_kind_of(described_class),
1060
- env: an_instance_of(Hash) }),
1061
- have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: a_kind_of(described_class),
1062
- filters: a_collection_containing_exactly(an_instance_of(Proc)),
1063
- type: :before }),
1064
- have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: a_kind_of(described_class),
1065
- filters: [],
1066
- type: :before_validation }),
1067
- have_attributes(name: 'endpoint_run_validators.grape', payload: { endpoint: a_kind_of(described_class),
1068
- validators: [],
1069
- request: a_kind_of(Grape::Request) }),
1070
- have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: a_kind_of(described_class),
1071
- filters: [],
1072
- type: :after_validation }),
1073
- have_attributes(name: 'endpoint_render.grape', payload: { endpoint: a_kind_of(described_class) }),
1074
- have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: a_kind_of(described_class),
1075
- filters: [],
1076
- type: :after }),
1077
- have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: a_kind_of(described_class),
1078
- filters: [],
1079
- type: :finally }),
1080
- have_attributes(name: 'format_response.grape', payload: { env: an_instance_of(Hash),
1081
- formatter: a_kind_of(Module) })
1082
- )
1083
- end
1084
- end
1085
- end