grape 1.7.0 → 1.8.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 (74) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +46 -0
  3. data/CONTRIBUTING.md +31 -1
  4. data/README.md +38 -8
  5. data/grape.gemspec +2 -2
  6. data/lib/grape/api.rb +2 -2
  7. data/lib/grape/content_types.rb +2 -8
  8. data/lib/grape/dsl/desc.rb +3 -2
  9. data/lib/grape/dsl/inside_route.rb +6 -6
  10. data/lib/grape/dsl/parameters.rb +6 -1
  11. data/lib/grape/dsl/request_response.rb +3 -2
  12. data/lib/grape/dsl/settings.rb +2 -6
  13. data/lib/grape/endpoint.rb +21 -19
  14. data/lib/grape/error_formatter/base.rb +1 -1
  15. data/lib/grape/exceptions/base.rb +4 -3
  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/middleware/auth/base.rb +1 -1
  24. data/lib/grape/middleware/error.rb +1 -1
  25. data/lib/grape/middleware/formatter.rb +1 -1
  26. data/lib/grape/middleware/stack.rb +1 -1
  27. data/lib/grape/middleware/versioner/header.rb +11 -19
  28. data/lib/grape/request.rb +1 -1
  29. data/lib/grape/router/attribute_translator.rb +1 -1
  30. data/lib/grape/router/route.rb +1 -3
  31. data/lib/grape/types/invalid_value.rb +8 -0
  32. data/lib/grape/util/cache.rb +1 -1
  33. data/lib/grape/util/lazy_value.rb +3 -11
  34. data/lib/grape/util/strict_hash_configuration.rb +3 -4
  35. data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
  36. data/lib/grape/validations/params_scope.rb +9 -3
  37. data/lib/grape/validations/single_attribute_iterator.rb +3 -1
  38. data/lib/grape/validations/types/custom_type_coercer.rb +2 -16
  39. data/lib/grape/validations/types/invalid_value.rb +0 -7
  40. data/lib/grape/validations/validators/base.rb +9 -20
  41. data/lib/grape/validations/validators/default_validator.rb +2 -20
  42. data/lib/grape/validations/validators/multiple_params_base.rb +4 -8
  43. data/lib/grape/validations/validators/values_validator.rb +14 -5
  44. data/lib/grape/version.rb +1 -1
  45. data/lib/grape.rb +19 -3
  46. data/spec/grape/api/custom_validations_spec.rb +14 -57
  47. data/spec/grape/api_remount_spec.rb +36 -0
  48. data/spec/grape/api_spec.rb +15 -21
  49. data/spec/grape/dsl/desc_spec.rb +84 -85
  50. data/spec/grape/dsl/inside_route_spec.rb +6 -10
  51. data/spec/grape/dsl/request_response_spec.rb +21 -2
  52. data/spec/grape/endpoint_spec.rb +11 -10
  53. data/spec/grape/exceptions/body_parse_errors_spec.rb +40 -0
  54. data/spec/grape/exceptions/invalid_accept_header_spec.rb +3 -0
  55. data/spec/grape/exceptions/missing_group_type_spec.rb +5 -9
  56. data/spec/grape/exceptions/unsupported_group_type_spec.rb +5 -9
  57. data/spec/grape/grape_spec.rb +9 -0
  58. data/spec/grape/integration/rack_spec.rb +6 -5
  59. data/spec/grape/middleware/base_spec.rb +7 -5
  60. data/spec/grape/middleware/formatter_spec.rb +7 -7
  61. data/spec/grape/request_spec.rb +4 -14
  62. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +6 -8
  63. data/spec/grape/validations/single_attribute_iterator_spec.rb +8 -9
  64. data/spec/grape/validations/validators/base_spec.rb +38 -0
  65. data/spec/grape/validations/validators/values_spec.rb +56 -0
  66. data/spec/grape/validations_spec.rb +36 -12
  67. data/spec/shared/deprecated_class_examples.rb +16 -0
  68. metadata +112 -114
  69. data/lib/grape/config.rb +0 -34
  70. data/lib/grape/extensions/deep_mergeable_hash.rb +0 -21
  71. data/lib/grape/extensions/deep_symbolize_hash.rb +0 -32
  72. data/spec/grape/config_spec.rb +0 -17
  73. data/spec/grape/dsl/configuration_spec.rb +0 -14
  74. data/spec/grape/validations/attributes_iterator_spec.rb +0 -4
@@ -1,48 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- describe Grape::Validations do
4
- context 'deprecated Grape::Validations::Base' do
5
- subject do
6
- Class.new(Grape::API) do
7
- params do
8
- requires :text, validator_with_old_base: true
9
- end
10
- get do
11
- end
12
- end
13
- end
14
-
15
- let(:validator_with_old_base) do
16
- Class.new(Grape::Validations::Base) do
17
- def validate_param!(_attr_name, _params)
18
- true
19
- end
20
- end
21
- end
22
-
23
- before do
24
- described_class.register_validator('validator_with_old_base', validator_with_old_base)
25
- allow(Warning).to receive(:warn)
26
- end
3
+ require 'shared/deprecated_class_examples'
27
4
 
28
- after do
29
- described_class.deregister_validator('validator_with_old_base')
30
- end
31
-
32
- def app
33
- subject
5
+ describe Grape::Validations do
6
+ describe 'Grape::Validations::Base' do
7
+ let(:deprecated_class) do
8
+ Class.new(Grape::Validations::Base)
34
9
  end
35
10
 
36
- it 'puts a deprecation warning' do
37
- expect(Warning).to receive(:warn) do |message|
38
- expect(message).to include('`Grape::Validations::Base` is deprecated')
39
- end
40
-
41
- get '/'
42
- end
11
+ it_behaves_like 'deprecated class'
43
12
  end
44
13
 
45
- context 'using a custom length validator' do
14
+ describe 'using a custom length validator' do
46
15
  subject do
47
16
  Class.new(Grape::API) do
48
17
  params do
@@ -64,6 +33,7 @@ describe Grape::Validations do
64
33
  end
65
34
  end
66
35
  end
36
+ let(:app) { Rack::Builder.new(subject) }
67
37
 
68
38
  before do
69
39
  described_class.register_validator('default_length', default_length_validator)
@@ -73,10 +43,6 @@ describe Grape::Validations do
73
43
  described_class.deregister_validator('default_length')
74
44
  end
75
45
 
76
- def app
77
- subject
78
- end
79
-
80
46
  it 'under 140 characters' do
81
47
  get '/', text: 'abc'
82
48
  expect(last_response.status).to eq 200
@@ -96,7 +62,7 @@ describe Grape::Validations do
96
62
  end
97
63
  end
98
64
 
99
- context 'using a custom body-only validator' do
65
+ describe 'using a custom body-only validator' do
100
66
  subject do
101
67
  Class.new(Grape::API) do
102
68
  params do
@@ -115,6 +81,7 @@ describe Grape::Validations do
115
81
  end
116
82
  end
117
83
  end
84
+ let(:app) { Rack::Builder.new(subject) }
118
85
 
119
86
  before do
120
87
  described_class.register_validator('in_body', in_body_validator)
@@ -124,10 +91,6 @@ describe Grape::Validations do
124
91
  described_class.deregister_validator('in_body')
125
92
  end
126
93
 
127
- def app
128
- subject
129
- end
130
-
131
94
  it 'allows field in body' do
132
95
  get '/', text: 'abc'
133
96
  expect(last_response.status).to eq 200
@@ -141,7 +104,7 @@ describe Grape::Validations do
141
104
  end
142
105
  end
143
106
 
144
- context 'using a custom validator with message_key' do
107
+ describe 'using a custom validator with message_key' do
145
108
  subject do
146
109
  Class.new(Grape::API) do
147
110
  params do
@@ -160,6 +123,7 @@ describe Grape::Validations do
160
123
  end
161
124
  end
162
125
  end
126
+ let(:app) { Rack::Builder.new(subject) }
163
127
 
164
128
  before do
165
129
  described_class.register_validator('with_message_key', message_key_validator)
@@ -169,10 +133,6 @@ describe Grape::Validations do
169
133
  described_class.deregister_validator('with_message_key')
170
134
  end
171
135
 
172
- def app
173
- subject
174
- end
175
-
176
136
  it 'fails with message' do
177
137
  get '/', text: 'foobar'
178
138
  expect(last_response.status).to eq 400
@@ -180,7 +140,7 @@ describe Grape::Validations do
180
140
  end
181
141
  end
182
142
 
183
- context 'using a custom request/param validator' do
143
+ describe 'using a custom request/param validator' do
184
144
  subject do
185
145
  Class.new(Grape::API) do
186
146
  params do
@@ -208,6 +168,7 @@ describe Grape::Validations do
208
168
  end
209
169
  end
210
170
  end
171
+ let(:app) { Rack::Builder.new(subject) }
211
172
 
212
173
  before do
213
174
  described_class.register_validator('admin', admin_validator)
@@ -217,10 +178,6 @@ describe Grape::Validations do
217
178
  described_class.deregister_validator('admin')
218
179
  end
219
180
 
220
- def app
221
- subject
222
- end
223
-
224
181
  it 'fail when non-admin user sets an admin field' do
225
182
  get '/', admin_field: 'tester', non_admin_field: 'toaster'
226
183
  expect(last_response.status).to eq 400
@@ -147,6 +147,42 @@ describe Grape::API do
147
147
  end
148
148
  end
149
149
 
150
+ context 'when the params are configured via a configuration' do
151
+ subject(:a_remounted_api) do
152
+ Class.new(described_class) do
153
+ params do
154
+ requires configuration[:required_attr_name], type: String
155
+ end
156
+
157
+ get(mounted { configuration[:endpoint] }) do
158
+ status 200
159
+ end
160
+ end
161
+ end
162
+
163
+ context 'when the configured param is my_attr' do
164
+ it 'requires the configured params' do
165
+ root_api.mount a_remounted_api, with: {
166
+ required_attr_name: 'my_attr',
167
+ endpoint: 'test'
168
+ }
169
+ get 'test?another_attr=1'
170
+ expect(last_response.status).to eq 400
171
+ get 'test?my_attr=1'
172
+ expect(last_response.status).to eq 200
173
+
174
+ root_api.mount a_remounted_api, with: {
175
+ required_attr_name: 'another_attr',
176
+ endpoint: 'test_b'
177
+ }
178
+ get 'test_b?another_attr=1'
179
+ expect(last_response.status).to eq 200
180
+ get 'test_b?my_attr=1'
181
+ expect(last_response.status).to eq 400
182
+ end
183
+ end
184
+ end
185
+
150
186
  context 'when executing a standard block within a `mounted` block with all dynamic params' do
151
187
  subject(:a_remounted_api) do
152
188
  Class.new(described_class) do
@@ -103,22 +103,6 @@ describe Grape::API do
103
103
  }
104
104
  end
105
105
  end
106
-
107
- # Behavior as defined by rfc2616 when no header is defined
108
- # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
109
- describe 'no specified accept header' do
110
- # subject.version 'v1', using: :header
111
- # subject.get '/hello' do
112
- # 'hello'
113
- # end
114
-
115
- # it 'routes' do
116
- # get '/hello'
117
- # last_response.status.should eql 200
118
- # end
119
- end
120
-
121
- # pending 'routes if any media type is allowed'
122
106
  end
123
107
 
124
108
  describe '.version using accept_version_header' do
@@ -448,9 +432,10 @@ describe Grape::API do
448
432
  expect(last_response.body).to eql 'hiya'
449
433
  end
450
434
 
435
+ objects = ['string', :symbol, 1, -1.1, {}, [], true, false, nil].freeze
451
436
  %i[put post].each do |verb|
452
437
  context verb.to_s do
453
- ['string', :symbol, 1, -1.1, {}, [], true, false, nil].each do |object|
438
+ objects.each do |object|
454
439
  it "allows a(n) #{object.class} json object in params" do
455
440
  subject.format :json
456
441
  subject.send(verb) do
@@ -1601,7 +1586,7 @@ describe Grape::API do
1601
1586
 
1602
1587
  subject.get(:hello) { 'Hello, world.' }
1603
1588
  get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('allow', 'whatever')
1604
- expect(basic_auth_context).to be_a_kind_of(Grape::Endpoint)
1589
+ expect(basic_auth_context).to be_a(Grape::Endpoint)
1605
1590
  end
1606
1591
 
1607
1592
  it 'has access to helper methods' do
@@ -3067,7 +3052,7 @@ describe Grape::API do
3067
3052
  expect(route.description).to eq('first method')
3068
3053
  expect(route.route_foo).to be_nil
3069
3054
  expect(route.params).to eq({})
3070
- expect(route.options).to be_a_kind_of(Hash)
3055
+ expect(route.options).to be_a(Hash)
3071
3056
  end
3072
3057
 
3073
3058
  it 'has params which does not include format and version as named captures' do
@@ -3326,7 +3311,7 @@ describe Grape::API do
3326
3311
  it 'is able to cascade' do
3327
3312
  subject.mount lambda { |env|
3328
3313
  headers = {}
3329
- headers['X-Cascade'] == 'pass' unless env['PATH_INFO'].include?('boo')
3314
+ headers['X-Cascade'] == 'pass' if env['PATH_INFO'].exclude?('boo')
3330
3315
  [200, headers, ['Farfegnugen']]
3331
3316
  } => '/'
3332
3317
 
@@ -3725,7 +3710,7 @@ describe Grape::API do
3725
3710
  it 'sets the instance' do
3726
3711
  expect(subject.instance).to be_nil
3727
3712
  subject.compile
3728
- expect(subject.instance).to be_kind_of(subject.base_instance)
3713
+ expect(subject.instance).to be_a(subject.base_instance)
3729
3714
  end
3730
3715
  end
3731
3716
 
@@ -4241,6 +4226,15 @@ describe Grape::API do
4241
4226
  end
4242
4227
  end
4243
4228
 
4229
+ it 'does not override methods inherited from Class' do
4230
+ Class.define_method(:test_method) {}
4231
+ subclass = Class.new(described_class)
4232
+ expect(subclass).not_to receive(:add_setup)
4233
+ subclass.test_method
4234
+ ensure
4235
+ Class.remove_method(:test_method)
4236
+ end
4237
+
4244
4238
  context 'overriding via composition' do
4245
4239
  module Inherited
4246
4240
  def inherited(api)
@@ -1,99 +1,98 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Grape
4
- module DSL
5
- module DescSpec
6
- class Dummy
7
- extend Grape::DSL::Desc
8
- end
3
+ describe Grape::DSL::Desc do
4
+ subject { dummy_class }
5
+
6
+ let(:dummy_class) do
7
+ Class.new do
8
+ extend Grape::DSL::Desc
9
9
  end
10
- describe Desc do
11
- subject { Class.new(DescSpec::Dummy) }
10
+ end
12
11
 
13
- describe '.desc' do
14
- it 'sets a description' do
15
- desc_text = 'The description'
16
- options = { message: 'none' }
17
- subject.desc desc_text, options
18
- expect(subject.namespace_setting(:description)).to eq(options.merge(description: desc_text))
19
- expect(subject.route_setting(:description)).to eq(options.merge(description: desc_text))
20
- end
12
+ describe '.desc' do
13
+ it 'sets a description' do
14
+ desc_text = 'The description'
15
+ options = { message: 'none' }
16
+ subject.desc desc_text, options
17
+ expect(subject.namespace_setting(:description)).to eq(options.merge(description: desc_text))
18
+ expect(subject.route_setting(:description)).to eq(options.merge(description: desc_text))
19
+ end
21
20
 
22
- it 'can be set with a block' do
23
- expected_options = {
24
- summary: 'summary',
25
- description: 'The description',
26
- detail: 'more details',
27
- params: { first: :param },
28
- entity: Object,
29
- http_codes: [[401, 'Unauthorized', 'Entities::Error']],
30
- named: 'My named route',
31
- body_name: 'My body name',
32
- headers: [
33
- XAuthToken: {
34
- description: 'Valdates your identity',
35
- required: true
36
- },
37
- XOptionalHeader: {
38
- description: 'Not really needed',
39
- required: false
40
- }
41
- ],
42
- hidden: false,
43
- deprecated: false,
44
- is_array: true,
45
- nickname: 'nickname',
46
- produces: %w[array of mime_types],
47
- consumes: %w[array of mime_types],
48
- tags: %w[tag1 tag2],
49
- security: %w[array of security schemes]
21
+ it 'can be set with a block' do
22
+ expected_options = {
23
+ summary: 'summary',
24
+ description: 'The description',
25
+ detail: 'more details',
26
+ params: { first: :param },
27
+ entity: Object,
28
+ default: { code: 400, message: 'Invalid' },
29
+ http_codes: [[401, 'Unauthorized', 'Entities::Error']],
30
+ named: 'My named route',
31
+ body_name: 'My body name',
32
+ headers: [
33
+ XAuthToken: {
34
+ description: 'Valdates your identity',
35
+ required: true
36
+ },
37
+ XOptionalHeader: {
38
+ description: 'Not really needed',
39
+ required: false
50
40
  }
41
+ ],
42
+ hidden: false,
43
+ deprecated: false,
44
+ is_array: true,
45
+ nickname: 'nickname',
46
+ produces: %w[array of mime_types],
47
+ consumes: %w[array of mime_types],
48
+ tags: %w[tag1 tag2],
49
+ security: %w[array of security schemes]
50
+ }
51
51
 
52
- subject.desc 'The description' do
53
- summary 'summary'
54
- detail 'more details'
55
- params(first: :param)
56
- success Object
57
- failure [[401, 'Unauthorized', 'Entities::Error']]
58
- named 'My named route'
59
- body_name 'My body name'
60
- headers [
61
- XAuthToken: {
62
- description: 'Valdates your identity',
63
- required: true
64
- },
65
- XOptionalHeader: {
66
- description: 'Not really needed',
67
- required: false
68
- }
69
- ]
70
- hidden false
71
- deprecated false
72
- is_array true
73
- nickname 'nickname'
74
- produces %w[array of mime_types]
75
- consumes %w[array of mime_types]
76
- tags %w[tag1 tag2]
77
- security %w[array of security schemes]
78
- end
52
+ subject.desc 'The description' do
53
+ summary 'summary'
54
+ detail 'more details'
55
+ params(first: :param)
56
+ success Object
57
+ default code: 400, message: 'Invalid'
58
+ failure [[401, 'Unauthorized', 'Entities::Error']]
59
+ named 'My named route'
60
+ body_name 'My body name'
61
+ headers [
62
+ XAuthToken: {
63
+ description: 'Valdates your identity',
64
+ required: true
65
+ },
66
+ XOptionalHeader: {
67
+ description: 'Not really needed',
68
+ required: false
69
+ }
70
+ ]
71
+ hidden false
72
+ deprecated false
73
+ is_array true
74
+ nickname 'nickname'
75
+ produces %w[array of mime_types]
76
+ consumes %w[array of mime_types]
77
+ tags %w[tag1 tag2]
78
+ security %w[array of security schemes]
79
+ end
79
80
 
80
- expect(subject.namespace_setting(:description)).to eq(expected_options)
81
- expect(subject.route_setting(:description)).to eq(expected_options)
82
- end
81
+ expect(subject.namespace_setting(:description)).to eq(expected_options)
82
+ expect(subject.route_setting(:description)).to eq(expected_options)
83
+ end
83
84
 
84
- it 'can be set with options and a block' do
85
- expect(subject).to receive(:warn).with('[DEPRECATION] Passing a options hash and a block to `desc` is deprecated. Move all hash options to block.')
85
+ it 'can be set with options and a block' do
86
+ expect(ActiveSupport::Deprecation).to receive(:warn).with('Passing a options hash and a block to `desc` is deprecated. Move all hash options to block.')
86
87
 
87
- desc_text = 'The description'
88
- detail_text = 'more details'
89
- options = { message: 'none' }
90
- subject.desc desc_text, options do
91
- detail detail_text
92
- end
93
- expect(subject.namespace_setting(:description)).to eq(description: desc_text, detail: detail_text)
94
- expect(subject.route_setting(:description)).to eq(description: desc_text, detail: detail_text)
95
- end
88
+ desc_text = 'The description'
89
+ detail_text = 'more details'
90
+ options = { message: 'none' }
91
+ subject.desc desc_text, options do
92
+ detail detail_text
96
93
  end
94
+ expect(subject.namespace_setting(:description)).to eq(description: desc_text, detail: detail_text)
95
+ expect(subject.route_setting(:description)).to eq(description: desc_text, detail: detail_text)
97
96
  end
98
97
  end
99
98
  end
@@ -203,16 +203,12 @@ describe Grape::Endpoint do
203
203
  end
204
204
 
205
205
  describe '#file' do
206
- before do
207
- allow(subject).to receive(:warn)
208
- end
209
-
210
206
  describe 'set' do
211
207
  context 'as file path' do
212
208
  let(:file_path) { '/some/file/path' }
213
209
 
214
210
  it 'emits a warning that this method is deprecated' do
215
- expect(subject).to receive(:warn).with(/Use sendfile or stream/)
211
+ expect(ActiveSupport::Deprecation).to receive(:warn).with(/Use sendfile or stream/)
216
212
 
217
213
  subject.file file_path
218
214
  end
@@ -228,7 +224,7 @@ describe Grape::Endpoint do
228
224
  let(:file_object) { double('StreamerObject', each: nil) }
229
225
 
230
226
  it 'emits a warning that this method is deprecated' do
231
- expect(subject).to receive(:warn).with(/Use stream to use a Stream object/)
227
+ expect(ActiveSupport::Deprecation).to receive(:warn).with(/Use stream to use a Stream object/)
232
228
 
233
229
  subject.file file_object
234
230
  end
@@ -243,7 +239,7 @@ describe Grape::Endpoint do
243
239
 
244
240
  describe 'get' do
245
241
  it 'emits a warning that this method is deprecated' do
246
- expect(subject).to receive(:warn).with(/Use sendfile or stream/)
242
+ expect(ActiveSupport::Deprecation).to receive(:warn).with(/Use sendfile or stream/)
247
243
 
248
244
  subject.file
249
245
  end
@@ -273,7 +269,7 @@ describe Grape::Endpoint do
273
269
  end
274
270
 
275
271
  it 'sends no deprecation warnings' do
276
- expect(subject).not_to receive(:warn)
272
+ expect(ActiveSupport::Deprecation).not_to receive(:warn)
277
273
 
278
274
  subject.sendfile file_path
279
275
  end
@@ -334,7 +330,7 @@ describe Grape::Endpoint do
334
330
  end
335
331
 
336
332
  it 'emits no deprecation warnings' do
337
- expect(subject).not_to receive(:warn)
333
+ expect(ActiveSupport::Deprecation).not_to receive(:warn)
338
334
 
339
335
  subject.stream file_path
340
336
  end
@@ -384,7 +380,7 @@ describe Grape::Endpoint do
384
380
  end
385
381
 
386
382
  it 'emits no deprecation warnings' do
387
- expect(subject).not_to receive(:warn)
383
+ expect(ActiveSupport::Deprecation).not_to receive(:warn)
388
384
 
389
385
  subject.stream stream_object
390
386
  end
@@ -148,13 +148,32 @@ module Grape
148
148
  it 'sets rescue all to true' do
149
149
  expect(subject).to receive(:namespace_inheritable).with(:rescue_all, true)
150
150
  expect(subject).to receive(:namespace_inheritable).with(:rescue_grape_exceptions, true)
151
+ expect(subject).to receive(:namespace_inheritable).with(:grape_exceptions_rescue_handler, nil)
151
152
  subject.rescue_from :grape_exceptions
152
153
  end
153
154
 
154
- it 'sets rescue_grape_exceptions to true' do
155
+ it 'sets given proc as rescue handler' do
156
+ rescue_handler_proc = proc {}
155
157
  expect(subject).to receive(:namespace_inheritable).with(:rescue_all, true)
156
158
  expect(subject).to receive(:namespace_inheritable).with(:rescue_grape_exceptions, true)
157
- subject.rescue_from :grape_exceptions
159
+ expect(subject).to receive(:namespace_inheritable).with(:grape_exceptions_rescue_handler, rescue_handler_proc)
160
+ subject.rescue_from :grape_exceptions, rescue_handler_proc
161
+ end
162
+
163
+ it 'sets given block as rescue handler' do
164
+ rescue_handler_proc = proc {}
165
+ expect(subject).to receive(:namespace_inheritable).with(:rescue_all, true)
166
+ expect(subject).to receive(:namespace_inheritable).with(:rescue_grape_exceptions, true)
167
+ expect(subject).to receive(:namespace_inheritable).with(:grape_exceptions_rescue_handler, rescue_handler_proc)
168
+ subject.rescue_from :grape_exceptions, &rescue_handler_proc
169
+ end
170
+
171
+ it 'sets a rescue handler declared through :with option' do
172
+ with_block = -> { 'hello' }
173
+ expect(subject).to receive(:namespace_inheritable).with(:rescue_all, true)
174
+ expect(subject).to receive(:namespace_inheritable).with(:rescue_grape_exceptions, true)
175
+ expect(subject).to receive(:namespace_inheritable).with(:grape_exceptions_rescue_handler, an_instance_of(Proc))
176
+ subject.rescue_from :grape_exceptions, with: with_block
158
177
  end
159
178
  end
160
179
 
@@ -75,7 +75,7 @@ describe Grape::Endpoint do
75
75
  it 'sets itself in the env upon call' do
76
76
  subject.get('/') { 'Hello world.' }
77
77
  get '/'
78
- expect(last_request.env['api.endpoint']).to be_kind_of(described_class)
78
+ expect(last_request.env['api.endpoint']).to be_a(described_class)
79
79
  end
80
80
 
81
81
  describe '#status' do
@@ -138,7 +138,7 @@ describe Grape::Endpoint do
138
138
 
139
139
  it 'includes request headers' do
140
140
  get '/headers'
141
- expect(JSON.parse(last_response.body)).to eq(
141
+ expect(JSON.parse(last_response.body)).to include(
142
142
  'Host' => 'example.org',
143
143
  'Cookie' => ''
144
144
  )
@@ -173,7 +173,7 @@ describe Grape::Endpoint do
173
173
 
174
174
  get('/get/cookies')
175
175
 
176
- expect(last_response.headers['Set-Cookie'].split("\n").sort).to eql [
176
+ expect(Array(last_response.headers['Set-Cookie']).flat_map { |h| h.split("\n") }.sort).to eql [
177
177
  'cookie3=symbol',
178
178
  'cookie4=secret+code+here',
179
179
  'my-awesome-cookie1=is+cool',
@@ -198,8 +198,9 @@ describe Grape::Endpoint do
198
198
  end
199
199
  get('/username', {}, 'HTTP_COOKIE' => 'username=user; sandbox=false')
200
200
  expect(last_response.body).to eq('user_test')
201
- expect(last_response.headers['Set-Cookie']).to match(/username=user_test/)
202
- expect(last_response.headers['Set-Cookie']).to match(/sandbox=true/)
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/)
203
204
  end
204
205
 
205
206
  it 'deletes cookie' do
@@ -213,10 +214,10 @@ describe Grape::Endpoint do
213
214
  end
214
215
  get '/test', {}, 'HTTP_COOKIE' => 'delete_this_cookie=1; and_this=2'
215
216
  expect(last_response.body).to eq('3')
216
- cookies = last_response.headers['Set-Cookie'].split("\n").map do |set_cookie|
217
+ cookies = Array(last_response.headers['Set-Cookie']).flat_map { |h| h.split("\n") }.to_h do |set_cookie|
217
218
  cookie = CookieJar::Cookie.from_set_cookie 'http://localhost/test', set_cookie
218
219
  [cookie.name, cookie]
219
- end.to_h
220
+ end
220
221
  expect(cookies.size).to eq(2)
221
222
  %w[and_this delete_this_cookie].each do |cookie_name|
222
223
  cookie = cookies[cookie_name]
@@ -237,10 +238,10 @@ describe Grape::Endpoint do
237
238
  end
238
239
  get('/test', {}, 'HTTP_COOKIE' => 'delete_this_cookie=1; and_this=2')
239
240
  expect(last_response.body).to eq('3')
240
- cookies = last_response.headers['Set-Cookie'].split("\n").map do |set_cookie|
241
+ cookies = Array(last_response.headers['Set-Cookie']).flat_map { |h| h.split("\n") }.to_h do |set_cookie|
241
242
  cookie = CookieJar::Cookie.from_set_cookie 'http://localhost/test', set_cookie
242
243
  [cookie.name, cookie]
243
- end.to_h
244
+ end
244
245
  expect(cookies.size).to eq(2)
245
246
  %w[and_this delete_this_cookie].each do |cookie_name|
246
247
  cookie = cookies[cookie_name]
@@ -432,7 +433,7 @@ describe Grape::Endpoint do
432
433
  end
433
434
  post '/upload', { file: '' }, 'CONTENT_TYPE' => 'multipart/form-data; boundary=foobar'
434
435
  expect(last_response.status).to eq(400)
435
- expect(last_response.body).to eq('empty message body supplied with multipart/form-data; boundary=foobar content-type')
436
+ expect(last_response.body).to eq('file is invalid')
436
437
  end
437
438
  end
438
439