grape 1.2.3 → 1.2.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -36,6 +36,10 @@ module Grape
36
36
  configure_declared_params
37
37
  end
38
38
 
39
+ def configuration
40
+ @api.configuration.evaluate
41
+ end
42
+
39
43
  # @return [Boolean] whether or not this entire scope needs to be
40
44
  # validated
41
45
  def should_validate?(parameters)
@@ -52,6 +56,7 @@ module Grape
52
56
 
53
57
  return true unless @dependent_on
54
58
  return params.any? { |param| meets_dependency?(param, request_params) } if params.is_a?(Array)
59
+ return false unless params.respond_to?(:with_indifferent_access)
55
60
  params = params.with_indifferent_access
56
61
 
57
62
  @dependent_on.each do |dependency|
@@ -119,8 +124,8 @@ module Grape
119
124
  @parent.push_declared_params(attrs, opts)
120
125
  else
121
126
  if opts && opts[:as]
122
- @api.route_setting(:aliased_params, @api.route_setting(:aliased_params) || [])
123
- @api.route_setting(:aliased_params) << { attrs.first => opts[:as] }
127
+ @api.route_setting(:renamed_params, @api.route_setting(:renamed_params) || [])
128
+ @api.route_setting(:renamed_params) << { attrs.first => opts[:as] }
124
129
  attrs = [opts[:as]]
125
130
  end
126
131
 
@@ -2,12 +2,12 @@ module Grape
2
2
  module Validations
3
3
  class AsValidator < Base
4
4
  def initialize(attrs, options, required, scope, opts = {})
5
- @alias = options
5
+ @renamed_options = options
6
6
  super
7
7
  end
8
8
 
9
9
  def validate_param!(attr_name, params)
10
- params[@alias] = params[attr_name]
10
+ params[@renamed_options] = params[attr_name]
11
11
  end
12
12
  end
13
13
  end
@@ -1,4 +1,4 @@
1
1
  module Grape
2
2
  # The current version of Grape.
3
- VERSION = '1.2.3'.freeze
3
+ VERSION = '1.2.4'.freeze
4
4
  end
Binary file
@@ -63,22 +63,245 @@ describe Grape::API do
63
63
  end
64
64
  end
65
65
 
66
- context 'with a dynamically configured route' do
67
- before do
68
- a_remounted_api.namespace 'api' do
69
- get "/#{configuration[:path]}" do
70
- '10 votes'
66
+ describe 'with dynamic configuration' do
67
+ context 'when mounting an endpoint conditional on a configuration' do
68
+ subject(:a_remounted_api) do
69
+ Class.new(Grape::API) do
70
+ get 'always' do
71
+ 'success'
72
+ end
73
+
74
+ given configuration[:mount_sometimes] do
75
+ get 'sometimes' do
76
+ 'sometimes'
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+ it 'mounts the endpoints only when configured to do so' do
83
+ root_api.mount({ a_remounted_api => 'with_conditional' }, with: { mount_sometimes: true })
84
+ root_api.mount({ a_remounted_api => 'without_conditional' }, with: { mount_sometimes: false })
85
+
86
+ get '/with_conditional/always'
87
+ expect(last_response.body).to eq 'success'
88
+
89
+ get '/with_conditional/sometimes'
90
+ expect(last_response.body).to eq 'sometimes'
91
+
92
+ get '/without_conditional/always'
93
+ expect(last_response.body).to eq 'success'
94
+
95
+ get '/without_conditional/sometimes'
96
+ expect(last_response.status).to eq 404
97
+ end
98
+ end
99
+
100
+ context 'when executing a custom block on mount' do
101
+ subject(:a_remounted_api) do
102
+ Class.new(Grape::API) do
103
+ get 'always' do
104
+ 'success'
105
+ end
106
+
107
+ mounted do
108
+ configuration[:endpoints].each do |endpoint_name, endpoint_response|
109
+ get endpoint_name do
110
+ endpoint_response
111
+ end
112
+ end
113
+ end
71
114
  end
72
115
  end
73
- root_api.mount a_remounted_api, with: { path: 'votes' }
74
- root_api.mount a_remounted_api, with: { path: 'scores' }
116
+
117
+ it 'mounts the endpoints only when configured to do so' do
118
+ root_api.mount a_remounted_api, with: { endpoints: { 'api_name' => 'api_response' } }
119
+ get 'api_name'
120
+ expect(last_response.body).to eq 'api_response'
121
+ end
75
122
  end
76
123
 
77
- it 'will use the dynamic configuration on all routes' do
78
- get 'api/votes'
79
- expect(last_response.body).to eql '10 votes'
80
- get 'api/scores'
81
- expect(last_response.body).to eql '10 votes'
124
+ context 'when the configuration is part of the arguments of a method' do
125
+ subject(:a_remounted_api) do
126
+ Class.new(Grape::API) do
127
+ get configuration[:endpoint_name] do
128
+ 'success'
129
+ end
130
+ end
131
+ end
132
+
133
+ it 'mounts the endpoint in the location it is configured' do
134
+ root_api.mount a_remounted_api, with: { endpoint_name: 'some_location' }
135
+ get '/some_location'
136
+ expect(last_response.body).to eq 'success'
137
+
138
+ get '/different_location'
139
+ expect(last_response.status).to eq 404
140
+
141
+ root_api.mount a_remounted_api, with: { endpoint_name: 'new_location' }
142
+ get '/new_location'
143
+ expect(last_response.body).to eq 'success'
144
+ end
145
+
146
+ context 'when the configuration is the value in a key-arg pair' do
147
+ subject(:a_remounted_api) do
148
+ Class.new(Grape::API) do
149
+ version 'v1', using: :param, parameter: configuration[:version_param]
150
+ get 'endpoint' do
151
+ 'version 1'
152
+ end
153
+
154
+ version 'v2', using: :param, parameter: configuration[:version_param]
155
+ get 'endpoint' do
156
+ 'version 2'
157
+ end
158
+ end
159
+ end
160
+
161
+ it 'takes the param from the configuration' do
162
+ root_api.mount a_remounted_api, with: { version_param: 'param_name' }
163
+
164
+ get '/endpoint?param_name=v1'
165
+ expect(last_response.body).to eq 'version 1'
166
+
167
+ get '/endpoint?param_name=v2'
168
+ expect(last_response.body).to eq 'version 2'
169
+
170
+ get '/endpoint?wrong_param_name=v2'
171
+ expect(last_response.body).to eq 'version 1'
172
+ end
173
+ end
174
+ end
175
+
176
+ context 'on the DescSCope' do
177
+ subject(:a_remounted_api) do
178
+ Class.new(Grape::API) do
179
+ desc 'The description of this' do
180
+ tags ['not_configurable_tag', configuration[:a_configurable_tag]]
181
+ end
182
+ get 'location' do
183
+ 'success'
184
+ end
185
+ end
186
+ end
187
+
188
+ it 'mounts the endpoint with the appropiate tags' do
189
+ root_api.mount({ a_remounted_api => 'integer' }, with: { a_configurable_tag: 'a configured tag' })
190
+ end
191
+ end
192
+
193
+ context 'on the ParamScope' do
194
+ subject(:a_remounted_api) do
195
+ Class.new(Grape::API) do
196
+ params do
197
+ requires configuration[:required_param], type: configuration[:required_type]
198
+ end
199
+
200
+ get 'location' do
201
+ 'success'
202
+ end
203
+ end
204
+ end
205
+
206
+ it 'mounts the endpoint in the location it is configured' do
207
+ root_api.mount({ a_remounted_api => 'string' }, with: { required_param: 'param_key', required_type: String })
208
+ root_api.mount({ a_remounted_api => 'integer' }, with: { required_param: 'param_integer', required_type: Integer })
209
+
210
+ get '/string/location', param_key: 'a'
211
+ expect(last_response.body).to eq 'success'
212
+
213
+ get '/string/location', param_integer: 1
214
+ expect(last_response.status).to eq 400
215
+
216
+ get '/integer/location', param_integer: 1
217
+ expect(last_response.body).to eq 'success'
218
+
219
+ get '/integer/location', param_integer: 'a'
220
+ expect(last_response.status).to eq 400
221
+ end
222
+
223
+ context 'on dynamic checks' do
224
+ subject(:a_remounted_api) do
225
+ Class.new(Grape::API) do
226
+ params do
227
+ optional :restricted_values, values: -> { [configuration[:allowed_value], 'always'] }
228
+ end
229
+
230
+ get 'location' do
231
+ 'success'
232
+ end
233
+ end
234
+ end
235
+
236
+ it 'can read the configuration on lambdas' do
237
+ root_api.mount a_remounted_api, with: { allowed_value: 'sometimes' }
238
+ get '/location', restricted_values: 'always'
239
+ expect(last_response.body).to eq 'success'
240
+ get '/location', restricted_values: 'sometimes'
241
+ expect(last_response.body).to eq 'success'
242
+ get '/location', restricted_values: 'never'
243
+ expect(last_response.status).to eq 400
244
+ end
245
+ end
246
+ end
247
+
248
+ context 'when the configuration is read within a namespace' do
249
+ before do
250
+ a_remounted_api.namespace 'api' do
251
+ get "/#{configuration[:path]}" do
252
+ '10 votes'
253
+ end
254
+ end
255
+ root_api.mount a_remounted_api, with: { path: 'votes' }
256
+ root_api.mount a_remounted_api, with: { path: 'scores' }
257
+ end
258
+
259
+ it 'will use the dynamic configuration on all routes' do
260
+ get 'api/votes'
261
+ expect(last_response.body).to eql '10 votes'
262
+ get 'api/scores'
263
+ expect(last_response.body).to eql '10 votes'
264
+ end
265
+ end
266
+
267
+ context 'when the configuration is read in a helper' do
268
+ subject(:a_remounted_api) do
269
+ Class.new(Grape::API) do
270
+ helpers do
271
+ def printed_response
272
+ configuration[:some_value]
273
+ end
274
+ end
275
+
276
+ get 'location' do
277
+ printed_response
278
+ end
279
+ end
280
+ end
281
+
282
+ it 'will use the dynamic configuration on all routes' do
283
+ root_api.mount(a_remounted_api, with: { some_value: 'response value' })
284
+
285
+ get '/location'
286
+ expect(last_response.body).to eq 'response value'
287
+ end
288
+ end
289
+
290
+ context 'when the configuration is read within the response block' do
291
+ subject(:a_remounted_api) do
292
+ Class.new(Grape::API) do
293
+ get 'location' do
294
+ configuration[:some_value]
295
+ end
296
+ end
297
+ end
298
+
299
+ it 'will use the dynamic configuration on all routes' do
300
+ root_api.mount(a_remounted_api, with: { some_value: 'response value' })
301
+
302
+ get '/location'
303
+ expect(last_response.body).to eq 'response value'
304
+ end
82
305
  end
83
306
  end
84
307
  end
@@ -1628,6 +1628,199 @@ XML
1628
1628
  end
1629
1629
  end
1630
1630
 
1631
+ describe 'lifecycle' do
1632
+ let!(:lifecycle) { [] }
1633
+ let!(:standard_cycle) do
1634
+ %i[before before_validation after_validation api_call after finally]
1635
+ end
1636
+
1637
+ let!(:validation_error) do
1638
+ %i[before before_validation finally]
1639
+ end
1640
+
1641
+ let!(:errored_cycle) do
1642
+ %i[before before_validation after_validation api_call finally]
1643
+ end
1644
+
1645
+ before do
1646
+ current_cycle = lifecycle
1647
+
1648
+ subject.before do
1649
+ current_cycle << :before
1650
+ end
1651
+
1652
+ subject.before_validation do
1653
+ current_cycle << :before_validation
1654
+ end
1655
+
1656
+ subject.after_validation do
1657
+ current_cycle << :after_validation
1658
+ end
1659
+
1660
+ subject.after do
1661
+ current_cycle << :after
1662
+ end
1663
+
1664
+ subject.finally do
1665
+ current_cycle << :finally
1666
+ end
1667
+ end
1668
+
1669
+ context 'when the api_call succeeds' do
1670
+ before do
1671
+ current_cycle = lifecycle
1672
+
1673
+ subject.get 'api_call' do
1674
+ current_cycle << :api_call
1675
+ end
1676
+ end
1677
+
1678
+ it 'follows the standard life_cycle' do
1679
+ get '/api_call'
1680
+ expect(lifecycle).to eq standard_cycle
1681
+ end
1682
+ end
1683
+
1684
+ context 'when the api_call has a controlled error' do
1685
+ before do
1686
+ current_cycle = lifecycle
1687
+
1688
+ subject.get 'api_call' do
1689
+ current_cycle << :api_call
1690
+ error!(:some_error)
1691
+ end
1692
+ end
1693
+
1694
+ it 'follows the errored life_cycle (skips after)' do
1695
+ get '/api_call'
1696
+ expect(lifecycle).to eq errored_cycle
1697
+ end
1698
+ end
1699
+
1700
+ context 'when the api_call has an exception' do
1701
+ before do
1702
+ current_cycle = lifecycle
1703
+
1704
+ subject.get 'api_call' do
1705
+ current_cycle << :api_call
1706
+ raise StandardError
1707
+ end
1708
+ end
1709
+
1710
+ it 'follows the errored life_cycle (skips after)' do
1711
+ expect { get '/api_call' }.to raise_error(StandardError)
1712
+ expect(lifecycle).to eq errored_cycle
1713
+ end
1714
+ end
1715
+
1716
+ context 'when the api_call fails validation' do
1717
+ before do
1718
+ current_cycle = lifecycle
1719
+
1720
+ subject.params do
1721
+ requires :some_param, type: String
1722
+ end
1723
+
1724
+ subject.get 'api_call' do
1725
+ current_cycle << :api_call
1726
+ end
1727
+ end
1728
+
1729
+ it 'follows the failed_validation cycle (skips after_validation, api_call & after)' do
1730
+ get '/api_call'
1731
+ expect(lifecycle).to eq validation_error
1732
+ end
1733
+ end
1734
+ end
1735
+
1736
+ describe '.finally' do
1737
+ let!(:code) { { has_executed: false } }
1738
+ let(:block_to_run) do
1739
+ code_to_execute = code
1740
+ proc do
1741
+ code_to_execute[:has_executed] = true
1742
+ end
1743
+ end
1744
+
1745
+ context 'when the ensure block has no exceptions' do
1746
+ before { subject.finally(&block_to_run) }
1747
+
1748
+ context 'when no API call is made' do
1749
+ it 'has not executed the ensure code' do
1750
+ expect(code[:has_executed]).to be false
1751
+ end
1752
+ end
1753
+
1754
+ context 'when no errors occurs' do
1755
+ before do
1756
+ subject.get '/no_exceptions' do
1757
+ 'success'
1758
+ end
1759
+ end
1760
+
1761
+ it 'executes the ensure code' do
1762
+ get '/no_exceptions'
1763
+ expect(last_response.body).to eq 'success'
1764
+ expect(code[:has_executed]).to be true
1765
+ end
1766
+
1767
+ context 'with a helper' do
1768
+ let(:block_to_run) do
1769
+ code_to_execute = code
1770
+ proc do
1771
+ code_to_execute[:value] = some_helper
1772
+ end
1773
+ end
1774
+
1775
+ before do
1776
+ subject.helpers do
1777
+ def some_helper
1778
+ 'some_value'
1779
+ end
1780
+ end
1781
+
1782
+ subject.get '/with_helpers' do
1783
+ 'success'
1784
+ end
1785
+ end
1786
+
1787
+ it 'has access to the helper' do
1788
+ get '/with_helpers'
1789
+ expect(code[:value]).to eq 'some_value'
1790
+ end
1791
+ end
1792
+ end
1793
+
1794
+ context 'when an unhandled occurs inside the API call' do
1795
+ before do
1796
+ subject.get '/unhandled_exception' do
1797
+ raise StandardError
1798
+ end
1799
+ end
1800
+
1801
+ it 'executes the ensure code' do
1802
+ expect { get '/unhandled_exception' }.to raise_error StandardError
1803
+ expect(code[:has_executed]).to be true
1804
+ end
1805
+ end
1806
+
1807
+ context 'when a handled error occurs inside the API call' do
1808
+ before do
1809
+ subject.rescue_from(StandardError) { error! 'handled' }
1810
+ subject.get '/handled_exception' do
1811
+ raise StandardError
1812
+ end
1813
+ end
1814
+
1815
+ it 'executes the ensure code' do
1816
+ get '/handled_exception'
1817
+ expect(code[:has_executed]).to be true
1818
+ expect(last_response.body).to eq 'handled'
1819
+ end
1820
+ end
1821
+ end
1822
+ end
1823
+
1631
1824
  describe '.rescue_from' do
1632
1825
  it 'does not rescue errors when rescue_from is not set' do
1633
1826
  subject.get '/exception' do
@@ -3676,4 +3869,21 @@ XML
3676
3869
  expect(grape_api.eql?(MyAPI))
3677
3870
  end
3678
3871
  end
3872
+
3873
+ describe 'const_missing' do
3874
+ subject(:grape_api) { Class.new(Grape::API) }
3875
+ let(:mounted) do
3876
+ Class.new(Grape::API) do
3877
+ get '/missing' do
3878
+ SomeRandomConstant
3879
+ end
3880
+ end
3881
+ end
3882
+
3883
+ before { subject.mount mounted => '/const' }
3884
+
3885
+ it 'raises an error' do
3886
+ expect { get '/const/missing' }.to raise_error(NameError).with_message(/SomeRandomConstant/)
3887
+ end
3888
+ end
3679
3889
  end