grape 0.11.0 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of grape might be problematic. Click here for more details.

Files changed (94) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +23 -80
  3. data/.travis.yml +1 -1
  4. data/CHANGELOG.md +27 -0
  5. data/Gemfile +1 -1
  6. data/Guardfile +1 -1
  7. data/LICENSE +1 -1
  8. data/README.md +131 -30
  9. data/Rakefile +1 -1
  10. data/UPGRADING.md +110 -1
  11. data/gemfiles/rails_3.gemfile +1 -1
  12. data/gemfiles/rails_4.gemfile +1 -1
  13. data/grape.gemspec +4 -4
  14. data/lib/grape.rb +92 -62
  15. data/lib/grape/api.rb +10 -10
  16. data/lib/grape/cookies.rb +1 -1
  17. data/lib/grape/dsl/configuration.rb +7 -7
  18. data/lib/grape/dsl/helpers.rb +3 -3
  19. data/lib/grape/dsl/inside_route.rb +50 -21
  20. data/lib/grape/dsl/parameters.rb +25 -6
  21. data/lib/grape/dsl/request_response.rb +1 -1
  22. data/lib/grape/dsl/routing.rb +11 -10
  23. data/lib/grape/dsl/settings.rb +1 -1
  24. data/lib/grape/endpoint.rb +21 -19
  25. data/lib/grape/error_formatter/json.rb +1 -1
  26. data/lib/grape/exceptions/base.rb +1 -1
  27. data/lib/grape/exceptions/validation.rb +1 -1
  28. data/lib/grape/exceptions/validation_errors.rb +2 -2
  29. data/lib/grape/formatter/base.rb +1 -1
  30. data/lib/grape/formatter/json.rb +1 -1
  31. data/lib/grape/formatter/serializable_hash.rb +4 -4
  32. data/lib/grape/formatter/txt.rb +1 -1
  33. data/lib/grape/formatter/xml.rb +1 -1
  34. data/lib/grape/http/headers.rb +27 -0
  35. data/lib/grape/http/request.rb +1 -1
  36. data/lib/grape/middleware/error.rb +10 -4
  37. data/lib/grape/middleware/formatter.rb +13 -9
  38. data/lib/grape/middleware/globals.rb +2 -1
  39. data/lib/grape/middleware/versioner/accept_version_header.rb +2 -2
  40. data/lib/grape/middleware/versioner/header.rb +4 -4
  41. data/lib/grape/middleware/versioner/param.rb +2 -2
  42. data/lib/grape/middleware/versioner/path.rb +1 -1
  43. data/lib/grape/namespace.rb +2 -1
  44. data/lib/grape/parser/json.rb +1 -1
  45. data/lib/grape/parser/xml.rb +1 -1
  46. data/lib/grape/path.rb +3 -3
  47. data/lib/grape/presenters/presenter.rb +9 -0
  48. data/lib/grape/validations/params_scope.rb +3 -3
  49. data/lib/grape/validations/validators/allow_blank.rb +1 -1
  50. data/lib/grape/validations/validators/coerce.rb +6 -5
  51. data/lib/grape/validations/validators/default.rb +2 -2
  52. data/lib/grape/validations/validators/multiple_params_base.rb +1 -0
  53. data/lib/grape/validations/validators/regexp.rb +1 -1
  54. data/lib/grape/version.rb +1 -1
  55. data/spec/grape/api/custom_validations_spec.rb +47 -0
  56. data/spec/grape/api/deeply_included_options_spec.rb +56 -0
  57. data/spec/grape/api_spec.rb +64 -42
  58. data/spec/grape/dsl/configuration_spec.rb +2 -2
  59. data/spec/grape/dsl/helpers_spec.rb +1 -1
  60. data/spec/grape/dsl/inside_route_spec.rb +75 -19
  61. data/spec/grape/dsl/parameters_spec.rb +59 -10
  62. data/spec/grape/dsl/request_response_spec.rb +62 -2
  63. data/spec/grape/dsl/routing_spec.rb +116 -18
  64. data/spec/grape/endpoint_spec.rb +57 -5
  65. data/spec/grape/entity_spec.rb +1 -1
  66. data/spec/grape/exceptions/body_parse_errors_spec.rb +5 -5
  67. data/spec/grape/exceptions/invalid_accept_header_spec.rb +32 -32
  68. data/spec/grape/exceptions/validation_errors_spec.rb +1 -1
  69. data/spec/grape/integration/rack_spec.rb +4 -3
  70. data/spec/grape/middleware/auth/strategies_spec.rb +2 -2
  71. data/spec/grape/middleware/base_spec.rb +2 -2
  72. data/spec/grape/middleware/error_spec.rb +1 -1
  73. data/spec/grape/middleware/exception_spec.rb +5 -5
  74. data/spec/grape/middleware/formatter_spec.rb +10 -10
  75. data/spec/grape/middleware/globals_spec.rb +27 -0
  76. data/spec/grape/middleware/versioner/accept_version_header_spec.rb +1 -1
  77. data/spec/grape/middleware/versioner/header_spec.rb +1 -1
  78. data/spec/grape/middleware/versioner/param_spec.rb +1 -1
  79. data/spec/grape/middleware/versioner/path_spec.rb +1 -1
  80. data/spec/grape/path_spec.rb +6 -4
  81. data/spec/grape/presenters/presenter_spec.rb +70 -0
  82. data/spec/grape/util/inheritable_values_spec.rb +1 -1
  83. data/spec/grape/util/stackable_values_spec.rb +1 -1
  84. data/spec/grape/util/strict_hash_configuration_spec.rb +1 -1
  85. data/spec/grape/validations/params_scope_spec.rb +64 -0
  86. data/spec/grape/validations/validators/allow_blank_spec.rb +10 -0
  87. data/spec/grape/validations/validators/coerce_spec.rb +48 -18
  88. data/spec/grape/validations/validators/default_spec.rb +110 -20
  89. data/spec/grape/validations/validators/presence_spec.rb +41 -3
  90. data/spec/grape/validations/validators/regexp_spec.rb +7 -2
  91. data/spec/grape/validations_spec.rb +20 -1
  92. data/spec/support/file_streamer.rb +11 -0
  93. data/spec/support/versioned_helpers.rb +1 -1
  94. metadata +14 -2
@@ -47,7 +47,7 @@ module Grape
47
47
  description: 'Not really needed',
48
48
  required: false
49
49
  }
50
- ]
50
+ ]
51
51
  }
52
52
 
53
53
  subject.desc 'The description' do
@@ -64,7 +64,7 @@ module Grape
64
64
  description: 'Not really needed',
65
65
  required: false
66
66
  }
67
- ]
67
+ ]
68
68
  end
69
69
 
70
70
  expect(subject.namespace_setting(:description)).to eq(expected_options)
@@ -16,7 +16,7 @@ module Grape
16
16
  describe Helpers do
17
17
  subject { Class.new(HelpersSpec::Dummy) }
18
18
  let(:proc) do
19
- ->(*) do
19
+ lambda do |*|
20
20
  def test
21
21
  :test
22
22
  end
@@ -107,6 +107,25 @@ module Grape
107
107
  subject.status 501
108
108
  expect(subject.status).to eq 501
109
109
  end
110
+
111
+ it 'accepts symbol for status' do
112
+ subject.status :see_other
113
+ expect(subject.status).to eq 303
114
+ end
115
+
116
+ it 'raises error if unknow symbol is passed' do
117
+ expect { subject.status :foo_bar }
118
+ .to raise_error(ArgumentError, 'Status code :foo_bar is invalid.')
119
+ end
120
+
121
+ it 'accepts unknown Fixnum status codes' do
122
+ expect { subject.status 210 }.to_not raise_error
123
+ end
124
+
125
+ it 'raises error if status is not a fixnum or symbol' do
126
+ expect { subject.status Object.new }
127
+ .to raise_error(ArgumentError, 'Status code must be Fixnum or Symbol.')
128
+ end
110
129
  end
111
130
 
112
131
  describe '#header' do
@@ -176,6 +195,22 @@ module Grape
176
195
  end
177
196
  end
178
197
 
198
+ describe '#file' do
199
+ describe 'set' do
200
+ before do
201
+ subject.file 'file'
202
+ end
203
+
204
+ it 'returns value' do
205
+ expect(subject.file).to eq 'file'
206
+ end
207
+ end
208
+
209
+ it 'returns default' do
210
+ expect(subject.file).to be nil
211
+ end
212
+ end
213
+
179
214
  describe '#route' do
180
215
  before do
181
216
  subject.env['rack.routing_args'] = {}
@@ -220,30 +255,51 @@ module Grape
220
255
  end
221
256
  end
222
257
 
223
- describe 'with' do
224
- describe 'multiple entities' do
225
- let(:entity_mock1) do
226
- entity_mock1 = Object.new
227
- allow(entity_mock1).to receive(:represent).and_return(dummy1: 'dummy1')
228
- entity_mock1
258
+ describe 'multiple entities' do
259
+ let(:entity_mock1) do
260
+ entity_mock1 = Object.new
261
+ allow(entity_mock1).to receive(:represent).and_return(dummy1: 'dummy1')
262
+ entity_mock1
263
+ end
264
+
265
+ let(:entity_mock2) do
266
+ entity_mock2 = Object.new
267
+ allow(entity_mock2).to receive(:represent).and_return(dummy2: 'dummy2')
268
+ entity_mock2
269
+ end
270
+
271
+ describe 'instance' do
272
+ before do
273
+ subject.present 'dummy1', with: entity_mock1
274
+ subject.present 'dummy2', with: entity_mock2
229
275
  end
230
276
 
231
- let(:entity_mock2) do
232
- entity_mock2 = Object.new
233
- allow(entity_mock2).to receive(:represent).and_return(dummy2: 'dummy2')
234
- entity_mock2
277
+ it 'presents both dummy objects' do
278
+ expect(subject.body[:dummy1]).to eq 'dummy1'
279
+ expect(subject.body[:dummy2]).to eq 'dummy2'
235
280
  end
281
+ end
282
+ end
236
283
 
237
- describe 'instance' do
238
- before do
239
- subject.present 'dummy1', with: entity_mock1
240
- subject.present 'dummy2', with: entity_mock2
241
- end
284
+ describe 'non mergeable entity' do
285
+ let(:entity_mock1) do
286
+ entity_mock1 = Object.new
287
+ allow(entity_mock1).to receive(:represent).and_return(dummy1: 'dummy1')
288
+ entity_mock1
289
+ end
242
290
 
243
- it 'presents both dummy objects' do
244
- expect(subject.body[:dummy1]).to eq 'dummy1'
245
- expect(subject.body[:dummy2]).to eq 'dummy2'
246
- end
291
+ let(:entity_mock2) do
292
+ entity_mock2 = Object.new
293
+ allow(entity_mock2).to receive(:represent).and_return('not a hash')
294
+ entity_mock2
295
+ end
296
+
297
+ describe 'instance' do
298
+ it 'fails' do
299
+ subject.present 'dummy1', with: entity_mock1
300
+ expect do
301
+ subject.present 'dummy2', with: entity_mock2
302
+ end.to raise_error ArgumentError, 'Representation of type String cannot be merged.'
247
303
  end
248
304
  end
249
305
  end
@@ -5,6 +5,7 @@ module Grape
5
5
  module ParametersSpec
6
6
  class Dummy
7
7
  include Grape::DSL::Parameters
8
+ attr_accessor :api, :element, :parent
8
9
 
9
10
  def validate_attributes(*args)
10
11
  @validate_attributes = *args
@@ -41,16 +42,36 @@ module Grape
41
42
  describe Parameters do
42
43
  subject { ParametersSpec::Dummy.new }
43
44
 
44
- xdescribe '#use' do
45
- it 'does some thing'
45
+ describe '#use' do
46
+ before do
47
+ allow_message_expectations_on_nil
48
+ allow(subject.api).to receive(:namespace_stackable).with(:named_params)
49
+ end
50
+ let(:options) { { option: 'value' } }
51
+ let(:named_params) { { params_group: proc {} } }
52
+
53
+ it 'calls processes associated with named params' do
54
+ allow(Grape::DSL::Configuration).to receive(:stacked_hash_to_hash).and_return(named_params)
55
+ expect(subject).to receive(:instance_exec).with(options).and_yield
56
+ subject.use :params_group, options
57
+ end
58
+
59
+ it 'raises error when non-existent named param is called' do
60
+ allow(Grape::DSL::Configuration).to receive(:stacked_hash_to_hash).and_return({})
61
+ expect { subject.use :params_group }.to raise_error('Params :params_group not found!')
62
+ end
46
63
  end
47
64
 
48
- xdescribe '#use_scope' do
49
- it 'does some thing'
65
+ describe '#use_scope' do
66
+ it 'is alias to #use' do
67
+ expect(subject.method(:use_scope)).to eq subject.method(:use)
68
+ end
50
69
  end
51
70
 
52
- xdescribe '#includes' do
53
- it 'does some thing'
71
+ describe '#includes' do
72
+ it 'is alias to #use' do
73
+ expect(subject.method(:includes)).to eq subject.method(:use)
74
+ end
54
75
  end
55
76
 
56
77
  describe '#requires' do
@@ -103,12 +124,40 @@ module Grape
103
124
  end
104
125
  end
105
126
 
106
- xdescribe '#group' do
107
- it 'does some thing'
127
+ describe '#group' do
128
+ it 'is alias to #requires' do
129
+ expect(subject.method(:group)).to eq subject.method(:requires)
130
+ end
108
131
  end
109
132
 
110
- xdescribe '#params' do
111
- it 'does some thing'
133
+ describe '#params' do
134
+ it 'inherits params from parent' do
135
+ parent_params = { foo: 'bar' }
136
+ subject.parent = Object.new
137
+ allow(subject.parent).to receive(:params).and_return(parent_params)
138
+ expect(subject.params({})).to eq parent_params
139
+ end
140
+
141
+ describe 'when params argument is an array of hashes' do
142
+ it 'returns values of each hash for @element key' do
143
+ subject.element = :foo
144
+ expect(subject.params([{ foo: 'bar' }, { foo: 'baz' }])).to eq(%w(bar baz))
145
+ end
146
+ end
147
+
148
+ describe 'when params argument is a hash' do
149
+ it 'returns value for @element key' do
150
+ subject.element = :foo
151
+ expect(subject.params(foo: 'bar')).to eq('bar')
152
+ end
153
+ end
154
+
155
+ describe 'when params argument is not a array or a hash' do
156
+ it 'returns empty hash' do
157
+ subject.element = Object.new
158
+ expect(subject.params(Object.new)).to eq({})
159
+ end
160
+ end
112
161
  end
113
162
  end
114
163
  end
@@ -101,8 +101,68 @@ module Grape
101
101
  end
102
102
  end
103
103
 
104
- xdescribe '.rescue_from' do
105
- it 'does some thing'
104
+ describe '.rescue_from' do
105
+ describe ':all' do
106
+ it 'sets rescue all to true' do
107
+ expect(subject).to receive(:namespace_inheritable).with(:rescue_all, true)
108
+ expect(subject).to receive(:namespace_inheritable).with(:all_rescue_handler, nil)
109
+ subject.rescue_from :all
110
+ end
111
+
112
+ it 'sets given proc as rescue handler' do
113
+ rescue_handler_proc = proc {}
114
+ expect(subject).to receive(:namespace_inheritable).with(:rescue_all, true)
115
+ expect(subject).to receive(:namespace_inheritable).with(:all_rescue_handler, rescue_handler_proc)
116
+ subject.rescue_from :all, rescue_handler_proc
117
+ end
118
+
119
+ it 'sets given block as rescue handler' do
120
+ rescue_handler_proc = proc {}
121
+ expect(subject).to receive(:namespace_inheritable).with(:rescue_all, true)
122
+ expect(subject).to receive(:namespace_inheritable).with(:all_rescue_handler, rescue_handler_proc)
123
+ subject.rescue_from :all, &rescue_handler_proc
124
+ end
125
+
126
+ it 'sets a rescue handler declared through :with option' do
127
+ expect(subject).to receive(:namespace_inheritable).with(:rescue_all, true)
128
+ expect(subject).to receive(:namespace_inheritable).with(:all_rescue_handler, an_instance_of(Proc))
129
+ subject.rescue_from :all, with: 'ExampleHandler'
130
+ end
131
+ end
132
+
133
+ describe 'list of exceptions is passed' do
134
+ it 'sets hash of exceptions as rescue handlers' do
135
+ expect(subject).to receive(:namespace_stackable).with(:rescue_handlers, StandardError => nil)
136
+ expect(subject).to receive(:namespace_stackable).with(:rescue_options, {})
137
+ subject.rescue_from StandardError
138
+ end
139
+
140
+ it 'rescues only base handlers if rescue_subclasses: false option is passed' do
141
+ expect(subject).to receive(:namespace_stackable).with(:base_only_rescue_handlers, StandardError => nil)
142
+ expect(subject).to receive(:namespace_stackable).with(:rescue_options, rescue_subclasses: false)
143
+ subject.rescue_from StandardError, rescue_subclasses: false
144
+ end
145
+
146
+ it 'sets given proc as rescue handler for each key in hash' do
147
+ rescue_handler_proc = proc {}
148
+ expect(subject).to receive(:namespace_stackable).with(:rescue_handlers, StandardError => rescue_handler_proc)
149
+ expect(subject).to receive(:namespace_stackable).with(:rescue_options, {})
150
+ subject.rescue_from StandardError, rescue_handler_proc
151
+ end
152
+
153
+ it 'sets given block as rescue handler for each key in hash' do
154
+ rescue_handler_proc = proc {}
155
+ expect(subject).to receive(:namespace_stackable).with(:rescue_handlers, StandardError => rescue_handler_proc)
156
+ expect(subject).to receive(:namespace_stackable).with(:rescue_options, {})
157
+ subject.rescue_from StandardError, &rescue_handler_proc
158
+ end
159
+
160
+ it 'sets a rescue handler declared through :with option for each key in hash' do
161
+ expect(subject).to receive(:namespace_stackable).with(:rescue_handlers, StandardError => an_instance_of(Proc))
162
+ expect(subject).to receive(:namespace_stackable).with(:rescue_options, with: 'ExampleHandler')
163
+ subject.rescue_from StandardError, with: 'ExampleHandler'
164
+ end
165
+ end
106
166
  end
107
167
 
108
168
  describe '.represent' do
@@ -59,13 +59,53 @@ module Grape
59
59
 
60
60
  expect(subject.inheritable_setting.to_hash[:namespace]).to eq({})
61
61
  expect(subject.inheritable_setting.to_hash[:namespace_inheritable]).to eq({})
62
- expect(app1.inheritable_setting.to_hash[:namespace_stackable]).to eq(:mount_path => ['/app1'])
62
+ expect(app1.inheritable_setting.to_hash[:namespace_stackable]).to eq(mount_path: ['/app1'])
63
63
 
64
- expect(app2.inheritable_setting.to_hash[:namespace_stackable]).to eq(:mount_path => ['/app1', '/app2'])
64
+ expect(app2.inheritable_setting.to_hash[:namespace_stackable]).to eq(mount_path: ['/app1', '/app2'])
65
65
  end
66
66
  end
67
- xdescribe '.route' do
68
- it 'does some thing'
67
+
68
+ describe '.route' do
69
+ before do
70
+ allow(subject).to receive(:endpoints).and_return([])
71
+ allow(subject).to receive(:route_end)
72
+ allow(subject).to receive(:reset_validations!)
73
+ end
74
+
75
+ it 'marks end of the route' do
76
+ expect(subject).to receive(:route_end)
77
+ subject.route(:any)
78
+ end
79
+
80
+ it 'resets validations' do
81
+ expect(subject).to receive(:reset_validations!)
82
+ subject.route(:any)
83
+ end
84
+
85
+ it 'defines a new endpoint' do
86
+ expect { subject.route(:any) }
87
+ .to change { subject.endpoints.count }.from(0).to(1)
88
+ end
89
+
90
+ it 'does not duplicate identical endpoints' do
91
+ subject.route(:any)
92
+ expect { subject.route(:any) }
93
+ .to_not change(subject.endpoints, :count)
94
+ end
95
+
96
+ it 'generates correct endpoint options' do
97
+ allow(subject).to receive(:route_setting).with(:description).and_return(fiz: 'baz')
98
+ allow(Grape::DSL::Configuration).to receive(:stacked_hash_to_hash).and_return(nuz: 'naz')
99
+
100
+ expect(Grape::Endpoint).to receive(:new) do |_inheritable_setting, endpoint_options|
101
+ expect(endpoint_options[:method]).to eq :get
102
+ expect(endpoint_options[:path]).to eq '/foo'
103
+ expect(endpoint_options[:for]).to eq subject
104
+ expect(endpoint_options[:route_options]).to eq(foo: 'bar', fiz: 'baz', params: { nuz: 'naz' })
105
+ end.and_yield
106
+
107
+ subject.route(:get, '/foo', { foo: 'bar' }, &proc {})
108
+ end
69
109
  end
70
110
 
71
111
  describe '.get' do
@@ -117,32 +157,90 @@ module Grape
117
157
  end
118
158
  end
119
159
 
120
- xdescribe '.namespace' do
121
- it 'does some thing'
160
+ describe '.namespace' do
161
+ let(:new_namespace) { Object.new }
162
+
163
+ it 'creates a new namespace with given name and options' do
164
+ expect(subject).to receive(:within_namespace).and_yield
165
+ expect(subject).to receive(:nest).and_yield
166
+ expect(Namespace).to receive(:new).with(:foo, foo: 'bar').and_return(new_namespace)
167
+ expect(subject).to receive(:namespace_stackable).with(:namespace, new_namespace)
168
+
169
+ subject.namespace :foo, foo: 'bar', &proc {}
170
+ end
171
+
172
+ it 'calls #joined_space_path on Namespace' do
173
+ result_of_namspace_stackable = Object.new
174
+ allow(subject).to receive(:namespace_stackable).and_return(result_of_namspace_stackable)
175
+ expect(Namespace).to receive(:joined_space_path).with(result_of_namspace_stackable)
176
+ subject.namespace
177
+ end
122
178
  end
123
179
 
124
- xdescribe '.group' do
125
- it 'does some thing'
180
+ describe '.group' do
181
+ it 'is alias to #namespace' do
182
+ expect(subject.method(:group)).to eq subject.method(:namespace)
183
+ end
126
184
  end
127
185
 
128
- xdescribe '.resource' do
129
- it 'does some thing'
186
+ describe '.resource' do
187
+ it 'is alias to #namespace' do
188
+ expect(subject.method(:resource)).to eq subject.method(:namespace)
189
+ end
130
190
  end
131
191
 
132
- xdescribe '.resources' do
133
- it 'does some thing'
192
+ describe '.resources' do
193
+ it 'is alias to #namespace' do
194
+ expect(subject.method(:resources)).to eq subject.method(:namespace)
195
+ end
134
196
  end
135
197
 
136
- xdescribe '.segment' do
137
- it 'does some thing'
198
+ describe '.segment' do
199
+ it 'is alias to #namespace' do
200
+ expect(subject.method(:segment)).to eq subject.method(:namespace)
201
+ end
138
202
  end
139
203
 
140
- xdescribe '.routes' do
141
- it 'does some thing'
204
+ describe '.routes' do
205
+ let(:routes) { Object.new }
206
+
207
+ it 'returns value received from #prepare_routes' do
208
+ expect(subject).to receive(:prepare_routes).and_return(routes)
209
+ expect(subject.routes).to eq routes
210
+ end
211
+
212
+ context 'when #routes was already called once' do
213
+ before do
214
+ allow(subject).to receive(:prepare_routes).and_return(routes)
215
+ subject.routes
216
+ end
217
+ it 'it does not call prepare_routes again' do
218
+ expect(subject).to_not receive(:prepare_routes)
219
+ expect(subject.routes).to eq routes
220
+ end
221
+ end
142
222
  end
143
223
 
144
- xdescribe '.route_param' do
145
- it 'does some thing'
224
+ describe '.route_param' do
225
+ it 'calls #namespace with given params' do
226
+ expect(subject).to receive(:namespace).with(':foo', {}).and_yield
227
+ subject.route_param('foo', {}, &proc {})
228
+ end
229
+
230
+ let(:regex) { /(.*)/ }
231
+ let!(:options) { { requirements: regex } }
232
+ it 'nests requirements option under param name' do
233
+ expect(subject).to receive(:namespace) do |_param, options|
234
+ expect(options[:requirements][:foo]).to eq regex
235
+ end
236
+ subject.route_param('foo', options, &proc {})
237
+ end
238
+
239
+ it 'does not modify options parameter' do
240
+ allow(subject).to receive(:namespace)
241
+ expect { subject.route_param('foo', options, &proc {}) }
242
+ .to_not change { options }
243
+ end
146
244
  end
147
245
 
148
246
  describe '.versions' do