grape 1.7.1 → 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -1
  3. data/CONTRIBUTING.md +1 -1
  4. data/README.md +12 -4
  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 +1 -1
  9. data/lib/grape/dsl/inside_route.rb +5 -5
  10. data/lib/grape/dsl/request_response.rb +2 -1
  11. data/lib/grape/dsl/settings.rb +2 -6
  12. data/lib/grape/endpoint.rb +19 -17
  13. data/lib/grape/error_formatter/base.rb +1 -1
  14. data/lib/grape/exceptions/base.rb +2 -2
  15. data/lib/grape/exceptions/missing_group_type.rb +1 -6
  16. data/lib/grape/exceptions/unsupported_group_type.rb +1 -6
  17. data/lib/grape/exceptions/validation_errors.rb +1 -6
  18. data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +3 -3
  19. data/lib/grape/extensions/hash.rb +4 -7
  20. data/lib/grape/extensions/hashie/mash.rb +3 -3
  21. data/lib/grape/formatter/serializable_hash.rb +7 -7
  22. data/lib/grape/middleware/auth/base.rb +1 -1
  23. data/lib/grape/middleware/error.rb +1 -1
  24. data/lib/grape/middleware/formatter.rb +1 -1
  25. data/lib/grape/middleware/versioner/header.rb +11 -19
  26. data/lib/grape/router/route.rb +1 -3
  27. data/lib/grape/util/lazy_value.rb +3 -11
  28. data/lib/grape/util/strict_hash_configuration.rb +3 -4
  29. data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
  30. data/lib/grape/validations/params_scope.rb +8 -2
  31. data/lib/grape/validations/single_attribute_iterator.rb +3 -1
  32. data/lib/grape/validations/types/custom_type_coercer.rb +2 -16
  33. data/lib/grape/validations/validators/base.rb +9 -20
  34. data/lib/grape/validations/validators/default_validator.rb +2 -20
  35. data/lib/grape/validations/validators/multiple_params_base.rb +4 -8
  36. data/lib/grape/validations/validators/values_validator.rb +14 -5
  37. data/lib/grape/version.rb +1 -1
  38. data/lib/grape.rb +11 -3
  39. data/spec/grape/api/custom_validations_spec.rb +14 -57
  40. data/spec/grape/api_remount_spec.rb +36 -0
  41. data/spec/grape/api_spec.rb +10 -1
  42. data/spec/grape/dsl/desc_spec.rb +84 -87
  43. data/spec/grape/dsl/inside_route_spec.rb +6 -10
  44. data/spec/grape/dsl/request_response_spec.rb +21 -2
  45. data/spec/grape/endpoint_spec.rb +8 -8
  46. data/spec/grape/exceptions/body_parse_errors_spec.rb +40 -0
  47. data/spec/grape/exceptions/missing_group_type_spec.rb +5 -9
  48. data/spec/grape/exceptions/unsupported_group_type_spec.rb +5 -9
  49. data/spec/grape/grape_spec.rb +9 -0
  50. data/spec/grape/middleware/formatter_spec.rb +1 -1
  51. data/spec/grape/request_spec.rb +4 -14
  52. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +6 -8
  53. data/spec/grape/validations/single_attribute_iterator_spec.rb +8 -9
  54. data/spec/grape/validations/validators/base_spec.rb +38 -0
  55. data/spec/grape/validations/validators/values_spec.rb +37 -0
  56. data/spec/grape/validations_spec.rb +7 -6
  57. data/spec/shared/deprecated_class_examples.rb +16 -0
  58. metadata +17 -22
  59. data/lib/grape/config.rb +0 -34
  60. data/lib/grape/extensions/deep_mergeable_hash.rb +0 -21
  61. data/lib/grape/extensions/deep_symbolize_hash.rb +0 -32
  62. data/spec/grape/config_spec.rb +0 -17
@@ -1,101 +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
- default: { code: 400, message: 'Invalid' },
30
- http_codes: [[401, 'Unauthorized', 'Entities::Error']],
31
- named: 'My named route',
32
- body_name: 'My body name',
33
- headers: [
34
- XAuthToken: {
35
- description: 'Valdates your identity',
36
- required: true
37
- },
38
- XOptionalHeader: {
39
- description: 'Not really needed',
40
- required: false
41
- }
42
- ],
43
- hidden: false,
44
- deprecated: false,
45
- is_array: true,
46
- nickname: 'nickname',
47
- produces: %w[array of mime_types],
48
- consumes: %w[array of mime_types],
49
- tags: %w[tag1 tag2],
50
- 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
51
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
+ }
52
51
 
53
- subject.desc 'The description' do
54
- summary 'summary'
55
- detail 'more details'
56
- params(first: :param)
57
- success Object
58
- default code: 400, message: 'Invalid'
59
- failure [[401, 'Unauthorized', 'Entities::Error']]
60
- named 'My named route'
61
- body_name 'My body name'
62
- headers [
63
- XAuthToken: {
64
- description: 'Valdates your identity',
65
- required: true
66
- },
67
- XOptionalHeader: {
68
- description: 'Not really needed',
69
- required: false
70
- }
71
- ]
72
- hidden false
73
- deprecated false
74
- is_array true
75
- nickname 'nickname'
76
- produces %w[array of mime_types]
77
- consumes %w[array of mime_types]
78
- tags %w[tag1 tag2]
79
- security %w[array of security schemes]
80
- 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
81
80
 
82
- expect(subject.namespace_setting(:description)).to eq(expected_options)
83
- expect(subject.route_setting(:description)).to eq(expected_options)
84
- end
81
+ expect(subject.namespace_setting(:description)).to eq(expected_options)
82
+ expect(subject.route_setting(:description)).to eq(expected_options)
83
+ end
85
84
 
86
- it 'can be set with options and a block' do
87
- 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.')
88
87
 
89
- desc_text = 'The description'
90
- detail_text = 'more details'
91
- options = { message: 'none' }
92
- subject.desc desc_text, options do
93
- detail detail_text
94
- end
95
- expect(subject.namespace_setting(:description)).to eq(description: desc_text, detail: detail_text)
96
- expect(subject.route_setting(:description)).to eq(description: desc_text, detail: detail_text)
97
- 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
98
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)
99
96
  end
100
97
  end
101
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
 
@@ -138,10 +138,9 @@ 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
- 'Cookie' => '',
144
- 'Version' => 'HTTP/1.0'
143
+ 'Cookie' => ''
145
144
  )
146
145
  end
147
146
 
@@ -174,7 +173,7 @@ describe Grape::Endpoint do
174
173
 
175
174
  get('/get/cookies')
176
175
 
177
- 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 [
178
177
  'cookie3=symbol',
179
178
  'cookie4=secret+code+here',
180
179
  'my-awesome-cookie1=is+cool',
@@ -199,8 +198,9 @@ describe Grape::Endpoint do
199
198
  end
200
199
  get('/username', {}, 'HTTP_COOKIE' => 'username=user; sandbox=false')
201
200
  expect(last_response.body).to eq('user_test')
202
- expect(last_response.headers['Set-Cookie']).to match(/username=user_test/)
203
- 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/)
204
204
  end
205
205
 
206
206
  it 'deletes cookie' do
@@ -214,7 +214,7 @@ describe Grape::Endpoint do
214
214
  end
215
215
  get '/test', {}, 'HTTP_COOKIE' => 'delete_this_cookie=1; and_this=2'
216
216
  expect(last_response.body).to eq('3')
217
- cookies = last_response.headers['Set-Cookie'].split("\n").to_h do |set_cookie|
217
+ cookies = Array(last_response.headers['Set-Cookie']).flat_map { |h| h.split("\n") }.to_h do |set_cookie|
218
218
  cookie = CookieJar::Cookie.from_set_cookie 'http://localhost/test', set_cookie
219
219
  [cookie.name, cookie]
220
220
  end
@@ -238,7 +238,7 @@ describe Grape::Endpoint do
238
238
  end
239
239
  get('/test', {}, 'HTTP_COOKIE' => 'delete_this_cookie=1; and_this=2')
240
240
  expect(last_response.body).to eq('3')
241
- cookies = last_response.headers['Set-Cookie'].split("\n").to_h do |set_cookie|
241
+ cookies = Array(last_response.headers['Set-Cookie']).flat_map { |h| h.split("\n") }.to_h do |set_cookie|
242
242
  cookie = CookieJar::Cookie.from_set_cookie 'http://localhost/test', set_cookie
243
243
  [cookie.name, cookie]
244
244
  end
@@ -91,6 +91,46 @@ describe Grape::Exceptions::ValidationErrors do
91
91
  end
92
92
  end
93
93
 
94
+ context 'api with rescue_from :grape_exceptions handler with block' do
95
+ subject { Class.new(Grape::API) }
96
+
97
+ before do
98
+ subject.rescue_from :grape_exceptions do |e|
99
+ rack_response "Custom Error Contents, Original Message: #{e.message}", 400
100
+ end
101
+
102
+ subject.params do
103
+ requires :beer
104
+ end
105
+
106
+ subject.post '/beer' do
107
+ 'beer received'
108
+ end
109
+ end
110
+
111
+ def app
112
+ subject
113
+ end
114
+
115
+ context 'with content_type json' do
116
+ it 'returns body parsing error message' do
117
+ post '/beer', 'test', 'CONTENT_TYPE' => 'application/json'
118
+ expect(last_response.status).to eq 400
119
+ expect(last_response.body).to include 'message body does not match declared format'
120
+ expect(last_response.body).to include 'Custom Error Contents, Original Message'
121
+ end
122
+ end
123
+
124
+ context 'with content_type xml' do
125
+ it 'returns body parsing error message' do
126
+ post '/beer', 'test', 'CONTENT_TYPE' => 'application/xml'
127
+ expect(last_response.status).to eq 400
128
+ expect(last_response.body).to include 'message body does not match declared format'
129
+ expect(last_response.body).to include 'Custom Error Contents, Original Message'
130
+ end
131
+ end
132
+ end
133
+
94
134
  context 'api without a rescue handler' do
95
135
  subject { Class.new(Grape::API) }
96
136
 
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'shared/deprecated_class_examples'
4
+
3
5
  RSpec.describe Grape::Exceptions::MissingGroupType do
4
6
  describe '#message' do
5
7
  subject { described_class.new.message }
@@ -7,15 +9,9 @@ RSpec.describe Grape::Exceptions::MissingGroupType do
7
9
  it { is_expected.to include 'group type is required' }
8
10
  end
9
11
 
10
- describe 'deprecated Grape::Exceptions::MissingGroupTypeError' do
11
- subject { Grape::Exceptions::MissingGroupTypeError.new }
12
-
13
- it 'puts a deprecation warning' do
14
- expect(Warning).to receive(:warn) do |message|
15
- expect(message).to include('`Grape::Exceptions::MissingGroupTypeError` is deprecated')
16
- end
12
+ describe 'Grape::Exceptions::MissingGroupTypeError' do
13
+ let(:deprecated_class) { Grape::Exceptions::MissingGroupTypeError }
17
14
 
18
- subject
19
- end
15
+ it_behaves_like 'deprecated class'
20
16
  end
21
17
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'shared/deprecated_class_examples'
4
+
3
5
  RSpec.describe Grape::Exceptions::UnsupportedGroupType do
4
6
  subject { described_class.new }
5
7
 
@@ -9,15 +11,9 @@ RSpec.describe Grape::Exceptions::UnsupportedGroupType do
9
11
  it { is_expected.to include 'group type must be Array, Hash, JSON or Array[JSON]' }
10
12
  end
11
13
 
12
- describe 'deprecated Grape::Exceptions::UnsupportedGroupTypeError' do
13
- subject { Grape::Exceptions::UnsupportedGroupTypeError.new }
14
-
15
- it 'puts a deprecation warning' do
16
- expect(Warning).to receive(:warn) do |message|
17
- expect(message).to include('`Grape::Exceptions::UnsupportedGroupTypeError` is deprecated')
18
- end
14
+ describe 'Grape::Exceptions::UnsupportedGroupTypeError' do
15
+ let(:deprecated_class) { Grape::Exceptions::UnsupportedGroupTypeError }
19
16
 
20
- subject
21
- end
17
+ it_behaves_like 'deprecated class'
22
18
  end
23
19
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Grape do
4
+ describe '.config' do
5
+ subject { described_class.config }
6
+
7
+ it { is_expected.to eq(param_builder: Grape::Extensions::ActiveSupport::HashWithIndifferentAccess::ParamBuilder) }
8
+ end
9
+ end
@@ -405,7 +405,7 @@ describe Grape::Middleware::Formatter do
405
405
  env = { 'PATH_INFO' => '/somewhere', 'HTTP_ACCEPT' => 'application/json' }
406
406
  status, headers, body = subject.call(env)
407
407
  expect(status).to be == 200
408
- expect(headers).to be == { 'Content-Type' => 'application/json' }
408
+ expect(headers.transform_keys(&:downcase)).to be == { 'content-type' => 'application/json' }
409
409
  expect(read_chunks(body)).to be == ['data']
410
410
  end
411
411
  end
@@ -62,29 +62,19 @@ module Grape
62
62
  end
63
63
  end
64
64
 
65
- describe 'when the param_builder is set to Hashie' do
65
+ describe 'when the build_params_with is set to Hashie' do
66
66
  subject(:request_params) { described_class.new(env, **opts).params }
67
67
 
68
- before do
69
- Grape.configure do |config|
70
- config.param_builder = Grape::Extensions::Hashie::Mash::ParamBuilder
71
- end
72
- end
73
-
74
- after do
75
- Grape.config.reset
76
- end
77
-
78
68
  context 'when the API does not include a specific param builder' do
79
69
  let(:opts) { {} }
80
70
 
81
- it { is_expected.to be_a(Hashie::Mash) }
71
+ it { is_expected.to be_a(Hash) }
82
72
  end
83
73
 
84
74
  context 'when the API includes a specific param builder' do
85
- let(:opts) { { build_params_with: Grape::Extensions::Hash::ParamBuilder } }
75
+ let(:opts) { { build_params_with: Grape::Extensions::Hashie::Mash::ParamBuilder } }
86
76
 
87
- it { is_expected.to be_a(Hash) }
77
+ it { is_expected.to be_a(Hashie::Mash) }
88
78
  end
89
79
  end
90
80
 
@@ -12,8 +12,8 @@ describe Grape::Validations::MultipleAttributesIterator do
12
12
  { first: 'string', second: 'string' }
13
13
  end
14
14
 
15
- it 'yields the whole params hash and the skipped flag without the list of attrs' do
16
- expect { |b| iterator.each(&b) }.to yield_with_args(params, false)
15
+ it 'yields the whole params hash without the list of attrs' do
16
+ expect { |b| iterator.each(&b) }.to yield_with_args(params)
17
17
  end
18
18
  end
19
19
 
@@ -23,17 +23,15 @@ describe Grape::Validations::MultipleAttributesIterator do
23
23
  end
24
24
 
25
25
  it 'yields each element of the array without the list of attrs' do
26
- expect { |b| iterator.each(&b) }.to yield_successive_args([params[0], false], [params[1], false])
26
+ expect { |b| iterator.each(&b) }.to yield_successive_args(params[0], params[1])
27
27
  end
28
28
  end
29
29
 
30
30
  context 'when params is empty optional placeholder' do
31
- let(:params) do
32
- [Grape::DSL::Parameters::EmptyOptionalValue, { first: 'string2', second: 'string2' }]
33
- end
31
+ let(:params) { [Grape::DSL::Parameters::EmptyOptionalValue] }
34
32
 
35
- it 'yields each element of the array without the list of attrs' do
36
- expect { |b| iterator.each(&b) }.to yield_successive_args([Grape::DSL::Parameters::EmptyOptionalValue, true], [params[1], false])
33
+ it 'does not yield it' do
34
+ expect { |b| iterator.each(&b) }.to yield_successive_args
37
35
  end
38
36
  end
39
37
  end
@@ -14,7 +14,7 @@ describe Grape::Validations::SingleAttributeIterator do
14
14
 
15
15
  it 'yields params and every single attribute from the list' do
16
16
  expect { |b| iterator.each(&b) }
17
- .to yield_successive_args([params, :first, false, false], [params, :second, false, false])
17
+ .to yield_successive_args([params, :first, false], [params, :second, false])
18
18
  end
19
19
  end
20
20
 
@@ -25,8 +25,8 @@ describe Grape::Validations::SingleAttributeIterator do
25
25
 
26
26
  it 'yields every single attribute from the list for each of the array elements' do
27
27
  expect { |b| iterator.each(&b) }.to yield_successive_args(
28
- [params[0], :first, false, false], [params[0], :second, false, false],
29
- [params[1], :first, false, false], [params[1], :second, false, false]
28
+ [params[0], :first, false], [params[0], :second, false],
29
+ [params[1], :first, false], [params[1], :second, false]
30
30
  )
31
31
  end
32
32
 
@@ -35,9 +35,9 @@ describe Grape::Validations::SingleAttributeIterator do
35
35
 
36
36
  it 'marks params with empty values' do
37
37
  expect { |b| iterator.each(&b) }.to yield_successive_args(
38
- [params[0], :first, true, false], [params[0], :second, true, false],
39
- [params[1], :first, true, false], [params[1], :second, true, false],
40
- [params[2], :first, false, false], [params[2], :second, false, false]
38
+ [params[0], :first, true], [params[0], :second, true],
39
+ [params[1], :first, true], [params[1], :second, true],
40
+ [params[2], :first, false], [params[2], :second, false]
41
41
  )
42
42
  end
43
43
  end
@@ -45,10 +45,9 @@ describe Grape::Validations::SingleAttributeIterator do
45
45
  context 'when missing optional value' do
46
46
  let(:params) { [Grape::DSL::Parameters::EmptyOptionalValue, 10] }
47
47
 
48
- it 'marks params with skipped values' do
48
+ it 'does not yield skipped values' do
49
49
  expect { |b| iterator.each(&b) }.to yield_successive_args(
50
- [params[0], :first, false, true], [params[0], :second, false, true],
51
- [params[1], :first, false, false], [params[1], :second, false, false]
50
+ [params[1], :first, false], [params[1], :second, false]
52
51
  )
53
52
  end
54
53
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Grape::Validations::Validators::Base do
4
+ describe '#inherited' do
5
+ context 'when validator is anonymous' do
6
+ subject(:custom_validator) { Class.new(described_class) }
7
+
8
+ it 'does not register the validator' do
9
+ expect(Grape::Validations).not_to receive(:register_validator)
10
+ custom_validator
11
+ end
12
+ end
13
+
14
+ # Anonymous class does not have a name and class A < B would leak.
15
+ # Simulates inherited callback
16
+ context "when validator's underscored name does not end with _validator" do
17
+ subject(:custom_validator) { described_class.inherited(TestModule::CustomValidatorABC) }
18
+
19
+ before { stub_const('TestModule::CustomValidatorABC', Class.new) }
20
+
21
+ it 'registers the custom validator with a short name' do
22
+ expect(Grape::Validations).to receive(:register_validator).with('custom_validator_abc', TestModule::CustomValidatorABC)
23
+ custom_validator
24
+ end
25
+ end
26
+
27
+ context "when validator's underscored name ends with _validator" do
28
+ subject(:custom_validator) { described_class.inherited(TestModule::CustomValidator) }
29
+
30
+ before { stub_const('TestModule::CustomValidator', Class.new) }
31
+
32
+ it 'registers the custom validator with short name not ending with validator' do
33
+ expect(Grape::Validations).to receive(:register_validator).with('custom', TestModule::CustomValidator)
34
+ custom_validator
35
+ end
36
+ end
37
+ end
38
+ end