grape 1.3.1 → 1.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +82 -1
  3. data/LICENSE +1 -1
  4. data/README.md +104 -21
  5. data/UPGRADING.md +265 -39
  6. data/lib/grape.rb +2 -2
  7. data/lib/grape/api.rb +2 -2
  8. data/lib/grape/api/instance.rb +29 -28
  9. data/lib/grape/dsl/helpers.rb +1 -0
  10. data/lib/grape/dsl/inside_route.rb +69 -36
  11. data/lib/grape/dsl/parameters.rb +7 -3
  12. data/lib/grape/dsl/routing.rb +2 -4
  13. data/lib/grape/dsl/validations.rb +18 -1
  14. data/lib/grape/eager_load.rb +1 -1
  15. data/lib/grape/endpoint.rb +8 -6
  16. data/lib/grape/http/headers.rb +1 -0
  17. data/lib/grape/middleware/base.rb +2 -1
  18. data/lib/grape/middleware/error.rb +10 -12
  19. data/lib/grape/middleware/formatter.rb +3 -3
  20. data/lib/grape/middleware/stack.rb +21 -8
  21. data/lib/grape/middleware/versioner/header.rb +1 -1
  22. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +2 -1
  23. data/lib/grape/path.rb +2 -2
  24. data/lib/grape/request.rb +1 -1
  25. data/lib/grape/router.rb +30 -43
  26. data/lib/grape/router/attribute_translator.rb +26 -5
  27. data/lib/grape/router/route.rb +3 -22
  28. data/lib/grape/{serve_file → serve_stream}/file_body.rb +1 -1
  29. data/lib/grape/{serve_file → serve_stream}/sendfile_response.rb +1 -1
  30. data/lib/grape/{serve_file/file_response.rb → serve_stream/stream_response.rb} +8 -8
  31. data/lib/grape/util/base_inheritable.rb +11 -8
  32. data/lib/grape/util/lazy_value.rb +1 -0
  33. data/lib/grape/util/reverse_stackable_values.rb +3 -1
  34. data/lib/grape/util/stackable_values.rb +3 -1
  35. data/lib/grape/validations/attributes_iterator.rb +8 -0
  36. data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
  37. data/lib/grape/validations/params_scope.rb +8 -6
  38. data/lib/grape/validations/single_attribute_iterator.rb +1 -1
  39. data/lib/grape/validations/types.rb +6 -5
  40. data/lib/grape/validations/types/array_coercer.rb +14 -5
  41. data/lib/grape/validations/types/build_coercer.rb +5 -8
  42. data/lib/grape/validations/types/custom_type_coercer.rb +14 -2
  43. data/lib/grape/validations/types/dry_type_coercer.rb +36 -1
  44. data/lib/grape/validations/types/file.rb +15 -13
  45. data/lib/grape/validations/types/json.rb +40 -36
  46. data/lib/grape/validations/types/primitive_coercer.rb +11 -5
  47. data/lib/grape/validations/types/set_coercer.rb +6 -4
  48. data/lib/grape/validations/types/variant_collection_coercer.rb +1 -1
  49. data/lib/grape/validations/validator_factory.rb +1 -1
  50. data/lib/grape/validations/validators/as.rb +1 -1
  51. data/lib/grape/validations/validators/base.rb +4 -5
  52. data/lib/grape/validations/validators/coerce.rb +3 -10
  53. data/lib/grape/validations/validators/default.rb +3 -5
  54. data/lib/grape/validations/validators/except_values.rb +1 -1
  55. data/lib/grape/validations/validators/multiple_params_base.rb +2 -1
  56. data/lib/grape/validations/validators/regexp.rb +1 -1
  57. data/lib/grape/validations/validators/values.rb +1 -1
  58. data/lib/grape/version.rb +1 -1
  59. data/spec/grape/api/instance_spec.rb +50 -0
  60. data/spec/grape/api_spec.rb +75 -0
  61. data/spec/grape/dsl/inside_route_spec.rb +182 -33
  62. data/spec/grape/endpoint/declared_spec.rb +601 -0
  63. data/spec/grape/endpoint_spec.rb +0 -521
  64. data/spec/grape/entity_spec.rb +6 -0
  65. data/spec/grape/integration/rack_sendfile_spec.rb +12 -8
  66. data/spec/grape/middleware/auth/strategies_spec.rb +1 -1
  67. data/spec/grape/middleware/error_spec.rb +1 -1
  68. data/spec/grape/middleware/formatter_spec.rb +1 -1
  69. data/spec/grape/middleware/stack_spec.rb +3 -1
  70. data/spec/grape/path_spec.rb +4 -4
  71. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +13 -3
  72. data/spec/grape/validations/params_scope_spec.rb +26 -0
  73. data/spec/grape/validations/single_attribute_iterator_spec.rb +17 -6
  74. data/spec/grape/validations/types/array_coercer_spec.rb +35 -0
  75. data/spec/grape/validations/types/primitive_coercer_spec.rb +65 -5
  76. data/spec/grape/validations/types/set_coercer_spec.rb +34 -0
  77. data/spec/grape/validations/types_spec.rb +1 -1
  78. data/spec/grape/validations/validators/coerce_spec.rb +317 -29
  79. data/spec/grape/validations/validators/default_spec.rb +170 -0
  80. data/spec/grape/validations/validators/except_values_spec.rb +1 -0
  81. data/spec/grape/validations/validators/values_spec.rb +1 -1
  82. data/spec/grape/validations_spec.rb +290 -18
  83. data/spec/integration/eager_load/eager_load_spec.rb +15 -0
  84. data/spec/spec_helper.rb +0 -10
  85. data/spec/support/chunks.rb +14 -0
  86. data/spec/support/versioned_helpers.rb +3 -5
  87. metadata +18 -8
@@ -11,10 +11,10 @@ module Grape
11
11
  class PrimitiveCoercer < DryTypeCoercer
12
12
  MAPPING = {
13
13
  Grape::API::Boolean => DryTypes::Params::Bool,
14
+ BigDecimal => DryTypes::Params::Decimal,
14
15
 
15
16
  # unfortunately, a +Params+ scope doesn't contain String
16
- String => DryTypes::Coercible::String,
17
- BigDecimal => DryTypes::Coercible::Decimal
17
+ String => DryTypes::Coercible::String
18
18
  }.freeze
19
19
 
20
20
  STRICT_MAPPING = {
@@ -36,8 +36,7 @@ module Grape
36
36
 
37
37
  def call(val)
38
38
  return InvalidValue.new if reject?(val)
39
- return nil if val.nil?
40
- return '' if val == ''
39
+ return nil if val.nil? || treat_as_nil?(val)
41
40
 
42
41
  super
43
42
  end
@@ -46,7 +45,7 @@ module Grape
46
45
 
47
46
  attr_reader :type
48
47
 
49
- # This method maintaine logic which was defined by Virtus. For example,
48
+ # This method maintains logic which was defined by Virtus. For example,
50
49
  # dry-types is ok to convert an array or a hash to a string, it is supported,
51
50
  # but Virtus wouldn't accept it. So, this method only exists to not introduce
52
51
  # breaking changes.
@@ -55,6 +54,13 @@ module Grape
55
54
  (val.is_a?(String) && type == Hash) ||
56
55
  (val.is_a?(Hash) && type == String)
57
56
  end
57
+
58
+ # Dry-Types treats an empty string as invalid. However, Grape considers an empty string as
59
+ # absence of a value and coerces it into nil. See a discussion there
60
+ # https://github.com/ruby-grape/grape/pull/2045
61
+ def treat_as_nil?(val)
62
+ val == '' && type != String
63
+ end
58
64
  end
59
65
  end
60
66
  end
@@ -1,18 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'set'
4
- require_relative 'dry_type_coercer'
4
+ require_relative 'array_coercer'
5
5
 
6
6
  module Grape
7
7
  module Validations
8
8
  module Types
9
9
  # Takes the given array and converts it to a set. Every element of the set
10
10
  # is also coerced.
11
- class SetCoercer < DryTypeCoercer
11
+ class SetCoercer < ArrayCoercer
12
+ register_collection Set
13
+
12
14
  def initialize(type, strict = false)
13
15
  super
14
16
 
15
- @elem_coercer = PrimitiveCoercer.new(type.first, strict)
17
+ @coercer = nil
16
18
  end
17
19
 
18
20
  def call(value)
@@ -25,7 +27,7 @@ module Grape
25
27
 
26
28
  def coerce_elements(collection)
27
29
  collection.each_with_object(Set.new) do |elem, memo|
28
- coerced_elem = @elem_coercer.call(elem)
30
+ coerced_elem = elem_coercer.call(elem)
29
31
 
30
32
  return coerced_elem if coerced_elem.is_a?(InvalidValue)
31
33
 
@@ -33,7 +33,7 @@ module Grape
33
33
  # the coerced result, or an instance
34
34
  # of {InvalidValue} if the value could not be coerced.
35
35
  def call(value)
36
- return InvalidValue.new unless value.is_a? Array
36
+ return unless value.is_a? Array
37
37
 
38
38
  value =
39
39
  if @method
@@ -8,7 +8,7 @@ module Grape
8
8
  options[:options],
9
9
  options[:required],
10
10
  options[:params_scope],
11
- options[:opts])
11
+ **options[:opts])
12
12
  end
13
13
  end
14
14
  end
@@ -3,7 +3,7 @@
3
3
  module Grape
4
4
  module Validations
5
5
  class AsValidator < Base
6
- def initialize(attrs, options, required, scope, opts = {})
6
+ def initialize(attrs, options, required, scope, **opts)
7
7
  @renamed_options = options
8
8
  super
9
9
  end
@@ -13,7 +13,7 @@ module Grape
13
13
  # @param required [Boolean] attribute(s) are required or optional
14
14
  # @param scope [ParamsScope] parent scope for this Validator
15
15
  # @param opts [Hash] additional validation options
16
- def initialize(attrs, options, required, scope, opts = {})
16
+ def initialize(attrs, options, required, scope, **opts)
17
17
  @attrs = Array(attrs)
18
18
  @option = options
19
19
  @required = required
@@ -43,13 +43,12 @@ module Grape
43
43
  # there may be more than one error per field
44
44
  array_errors = []
45
45
 
46
- attributes.each do |val, attr_name, empty_val|
46
+ attributes.each do |val, attr_name, empty_val, skip_value|
47
+ next if skip_value
47
48
  next if !@scope.required? && empty_val
48
49
  next unless @scope.meets_dependency?(val, params)
49
50
  begin
50
- if @required || val.respond_to?(:key?) && val.key?(attr_name)
51
- validate_param!(attr_name, val)
52
- end
51
+ validate_param!(attr_name, val) if @required || val.respond_to?(:key?) && val.key?(attr_name)
53
52
  rescue Grape::Exceptions::Validation => e
54
53
  array_errors << e
55
54
  end
@@ -47,7 +47,9 @@ module Grape
47
47
  # h[:list] = list
48
48
  # h
49
49
  # => #<Hashie::Mash list=[1, 2, 3, 4]>
50
- params[attr_name] = new_value unless params[attr_name] == new_value
50
+ return if params[attr_name].class == new_value.class && params[attr_name] == new_value
51
+
52
+ params[attr_name] = new_value
51
53
  end
52
54
 
53
55
  private
@@ -65,16 +67,7 @@ module Grape
65
67
  end
66
68
 
67
69
  def coerce_value(val)
68
- # define default values for structures, the dry-types lib which is used
69
- # for coercion doesn't accept nil as a value, so it would fail
70
- if val.nil?
71
- return [] if type == Array || type.is_a?(Array)
72
- return Set.new if type == Set
73
- return {} if type == Hash
74
- end
75
-
76
70
  converter.call(val)
77
-
78
71
  # Some custom types might fail, so it should be treated as an invalid value
79
72
  rescue StandardError
80
73
  Types::InvalidValue.new
@@ -3,13 +3,12 @@
3
3
  module Grape
4
4
  module Validations
5
5
  class DefaultValidator < Base
6
- def initialize(attrs, options, required, scope, opts = {})
6
+ def initialize(attrs, options, required, scope, **opts)
7
7
  @default = options
8
8
  super
9
9
  end
10
10
 
11
11
  def validate_param!(attr_name, params)
12
- return if params.key? attr_name
13
12
  params[attr_name] = if @default.is_a? Proc
14
13
  @default.call
15
14
  elsif @default.frozen? || !duplicatable?(@default)
@@ -22,9 +21,8 @@ module Grape
22
21
  def validate!(params)
23
22
  attrs = SingleAttributeIterator.new(self, @scope, params)
24
23
  attrs.each do |resource_params, attr_name|
25
- if resource_params.is_a?(Hash) && resource_params[attr_name].nil?
26
- validate_param!(attr_name, resource_params)
27
- end
24
+ next unless @scope.meets_dependency?(resource_params, params)
25
+ validate_param!(attr_name, resource_params) if resource_params.is_a?(Hash) && resource_params[attr_name].nil?
28
26
  end
29
27
  end
30
28
 
@@ -3,7 +3,7 @@
3
3
  module Grape
4
4
  module Validations
5
5
  class ExceptValuesValidator < Base
6
- def initialize(attrs, options, required, scope, opts = {})
6
+ def initialize(attrs, options, required, scope, **opts)
7
7
  @except = options.is_a?(Hash) ? options[:value] : options
8
8
  super
9
9
  end
@@ -7,7 +7,8 @@ module Grape
7
7
  attributes = MultipleAttributesIterator.new(self, @scope, params)
8
8
  array_errors = []
9
9
 
10
- attributes.each do |resource_params|
10
+ attributes.each do |resource_params, skip_value|
11
+ next if skip_value
11
12
  begin
12
13
  validate_params!(resource_params)
13
14
  rescue Grape::Exceptions::Validation => e
@@ -5,7 +5,7 @@ module Grape
5
5
  class RegexpValidator < Base
6
6
  def validate_param!(attr_name, params)
7
7
  return unless params.respond_to?(:key?) && params.key?(attr_name)
8
- return if Array.wrap(params[attr_name]).all? { |param| param.nil? || (param.to_s =~ (options_key?(:value) ? @option[:value] : @option)) }
8
+ return if Array.wrap(params[attr_name]).all? { |param| param.nil? || param.to_s.match?((options_key?(:value) ? @option[:value] : @option)) }
9
9
  raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message(:regexp))
10
10
  end
11
11
  end
@@ -3,7 +3,7 @@
3
3
  module Grape
4
4
  module Validations
5
5
  class ValuesValidator < Base
6
- def initialize(attrs, options, required, scope, opts = {})
6
+ def initialize(attrs, options, required, scope, **opts)
7
7
  if options.is_a?(Hash)
8
8
  @excepts = options[:except]
9
9
  @values = options[:value]
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Grape
4
4
  # The current version of Grape.
5
- VERSION = '1.3.1'
5
+ VERSION = '1.5.1'
6
6
  end
@@ -51,4 +51,54 @@ describe Grape::API::Instance do
51
51
  expect(an_instance.top_level_setting.parent).to be_nil
52
52
  end
53
53
  end
54
+
55
+ context 'with multiple moutes' do
56
+ let(:first) do
57
+ Class.new(Grape::API::Instance) do
58
+ namespace(:some_namespace) do
59
+ route :any, '*path' do
60
+ error!('Not found! (1)', 404)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ let(:second) do
66
+ Class.new(Grape::API::Instance) do
67
+ namespace(:another_namespace) do
68
+ route :any, '*path' do
69
+ error!('Not found! (2)', 404)
70
+ end
71
+ end
72
+ end
73
+ end
74
+ let(:root_api) do
75
+ first_instance = first
76
+ second_instance = second
77
+ Class.new(Grape::API) do
78
+ mount first_instance
79
+ mount first_instance
80
+ mount second_instance
81
+ end
82
+ end
83
+
84
+ it 'does not raise a FrozenError on first instance' do
85
+ expect { patch '/some_namespace/anything' }.not_to \
86
+ raise_error
87
+ end
88
+
89
+ it 'responds the correct body at the first instance' do
90
+ patch '/some_namespace/anything'
91
+ expect(last_response.body).to eq 'Not found! (1)'
92
+ end
93
+
94
+ it 'does not raise a FrozenError on second instance' do
95
+ expect { get '/another_namespace/other' }.not_to \
96
+ raise_error
97
+ end
98
+
99
+ it 'responds the correct body at the second instance' do
100
+ get '/another_namespace/foobar'
101
+ expect(last_response.body).to eq 'Not found! (2)'
102
+ end
103
+ end
54
104
  end
@@ -816,6 +816,71 @@ XML
816
816
  end
817
817
  end
818
818
 
819
+ describe 'when hook behaviour is controlled by attributes on the route ' do
820
+ before do
821
+ subject.before do
822
+ error!('Access Denied', 401) unless route.options[:secret] == params[:secret]
823
+ end
824
+
825
+ subject.namespace 'example' do
826
+ before do
827
+ error!('Access Denied', 401) unless route.options[:namespace_secret] == params[:namespace_secret]
828
+ end
829
+
830
+ desc 'it gets with secret', secret: 'password'
831
+ get { status(params[:id] == '504' ? 200 : 404) }
832
+
833
+ desc 'it post with secret', secret: 'password', namespace_secret: 'namespace_password'
834
+ post {}
835
+ end
836
+ end
837
+
838
+ context 'when HTTP method is not defined' do
839
+ let(:response) { delete('/example') }
840
+
841
+ it 'responds with a 405 status' do
842
+ expect(response.status).to eql 405
843
+ end
844
+ end
845
+
846
+ context 'when HTTP method is defined with attribute' do
847
+ let(:response) { post('/example?secret=incorrect_password') }
848
+ it 'responds with the defined error in the before hook' do
849
+ expect(response.status).to eql 401
850
+ end
851
+ end
852
+
853
+ context 'when HTTP method is defined and the underlying before hook expectation is not met' do
854
+ let(:response) { post('/example?secret=password&namespace_secret=wrong_namespace_password') }
855
+ it 'ends up in the endpoint' do
856
+ expect(response.status).to eql 401
857
+ end
858
+ end
859
+
860
+ context 'when HTTP method is defined and everything is like the before hooks expect' do
861
+ let(:response) { post('/example?secret=password&namespace_secret=namespace_password') }
862
+ it 'ends up in the endpoint' do
863
+ expect(response.status).to eql 201
864
+ end
865
+ end
866
+
867
+ context 'when HEAD is called for the defined GET' do
868
+ let(:response) { head('/example?id=504') }
869
+
870
+ it 'responds with 401 because before expectations in before hooks are not met' do
871
+ expect(response.status).to eql 401
872
+ end
873
+ end
874
+
875
+ context 'when HEAD is called for the defined GET' do
876
+ let(:response) { head('/example?id=504&secret=password') }
877
+
878
+ it 'responds with 200 because before hooks are not called' do
879
+ expect(response.status).to eql 200
880
+ end
881
+ end
882
+ end
883
+
819
884
  context 'allows HEAD on a GET request that' do
820
885
  before do
821
886
  subject.get 'example' do
@@ -1084,6 +1149,11 @@ XML
1084
1149
  expect(last_response.headers['Content-Type']).to eq('text/plain')
1085
1150
  end
1086
1151
 
1152
+ it 'does not set Cache-Control' do
1153
+ get '/foo'
1154
+ expect(last_response.headers['Cache-Control']).to eq(nil)
1155
+ end
1156
+
1087
1157
  it 'sets content type for xml' do
1088
1158
  get '/foo.xml'
1089
1159
  expect(last_response.headers['Content-Type']).to eq('application/xml')
@@ -1530,6 +1600,11 @@ XML
1530
1600
  expect(subject.io).to receive(:write).with(message)
1531
1601
  subject.logger.info 'this will be logged'
1532
1602
  end
1603
+
1604
+ it 'does not unnecessarily retain duplicate setup blocks' do
1605
+ subject.logger
1606
+ expect { subject.logger }.to_not change(subject.instance_variable_get(:@setup), :size)
1607
+ end
1533
1608
  end
1534
1609
 
1535
1610
  describe '.helpers' do
@@ -203,80 +203,229 @@ 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
+
206
210
  describe 'set' do
207
211
  context 'as file path' do
208
212
  let(:file_path) { '/some/file/path' }
209
213
 
210
- let(:file_response) do
211
- file_body = Grape::ServeFile::FileBody.new(file_path)
212
- Grape::ServeFile::FileResponse.new(file_body)
213
- end
214
+ it 'emits a warning that this method is deprecated' do
215
+ expect(subject).to receive(:warn).with(/Use sendfile or stream/)
214
216
 
215
- before do
216
217
  subject.file file_path
217
218
  end
218
219
 
219
- it 'returns value wrapped in FileResponse' do
220
- expect(subject.file).to eq file_response
220
+ it 'forwards the call to sendfile' do
221
+ expect(subject).to receive(:sendfile).with(file_path)
222
+
223
+ subject.file file_path
221
224
  end
222
225
  end
223
226
 
224
227
  context 'as object (backward compatibility)' do
225
- let(:file_object) { Class.new }
228
+ let(:file_object) { double('StreamerObject', each: nil) }
229
+
230
+ it 'emits a warning that this method is deprecated' do
231
+ expect(subject).to receive(:warn).with(/Use stream to use a Stream object/)
232
+
233
+ subject.file file_object
234
+ end
235
+
236
+ it 'forwards the call to stream' do
237
+ expect(subject).to receive(:stream).with(file_object)
238
+
239
+ subject.file file_object
240
+ end
241
+ end
242
+ end
243
+
244
+ describe 'get' do
245
+ it 'emits a warning that this method is deprecated' do
246
+ expect(subject).to receive(:warn).with(/Use sendfile or stream/)
247
+
248
+ subject.file
249
+ end
250
+
251
+ it 'fowards call to sendfile' do
252
+ expect(subject).to receive(:sendfile)
253
+
254
+ subject.file
255
+ end
256
+ end
257
+ end
258
+
259
+ describe '#sendfile' do
260
+ describe 'set' do
261
+ context 'as file path' do
262
+ let(:file_path) { '/some/file/path' }
226
263
 
227
264
  let(:file_response) do
228
- Grape::ServeFile::FileResponse.new(file_object)
265
+ file_body = Grape::ServeStream::FileBody.new(file_path)
266
+ Grape::ServeStream::StreamResponse.new(file_body)
229
267
  end
230
268
 
231
269
  before do
232
- subject.file file_object
270
+ subject.header 'Cache-Control', 'cache'
271
+ subject.header 'Content-Length', 123
272
+ subject.header 'Transfer-Encoding', 'base64'
273
+ end
274
+
275
+ it 'sends no deprecation warnings' do
276
+ expect(subject).to_not receive(:warn)
277
+
278
+ subject.sendfile file_path
279
+ end
280
+
281
+ it 'returns value wrapped in StreamResponse' do
282
+ subject.sendfile file_path
283
+
284
+ expect(subject.sendfile).to eq file_response
285
+ end
286
+
287
+ it 'does not change the Cache-Control header' do
288
+ subject.sendfile file_path
289
+
290
+ expect(subject.header['Cache-Control']).to eq 'cache'
291
+ end
292
+
293
+ it 'does not change the Content-Length header' do
294
+ subject.sendfile file_path
295
+
296
+ expect(subject.header['Content-Length']).to eq 123
297
+ end
298
+
299
+ it 'does not change the Transfer-Encoding header' do
300
+ subject.sendfile file_path
301
+
302
+ expect(subject.header['Transfer-Encoding']).to eq 'base64'
233
303
  end
304
+ end
305
+
306
+ context 'as object' do
307
+ let(:file_object) { double('StreamerObject', each: nil) }
234
308
 
235
- it 'returns value wrapped in FileResponse' do
236
- expect(subject.file).to eq file_response
309
+ it 'raises an error that only a file path is supported' do
310
+ expect { subject.sendfile file_object }.to raise_error(ArgumentError, /Argument must be a file path/)
237
311
  end
238
312
  end
239
313
  end
240
314
 
241
315
  it 'returns default' do
242
- expect(subject.file).to be nil
316
+ expect(subject.sendfile).to be nil
243
317
  end
244
318
  end
245
319
 
246
320
  describe '#stream' do
247
321
  describe 'set' do
248
- let(:file_object) { Class.new }
322
+ context 'as a file path' do
323
+ let(:file_path) { '/some/file/path' }
249
324
 
250
- before do
251
- subject.header 'Cache-Control', 'cache'
252
- subject.header 'Content-Length', 123
253
- subject.header 'Transfer-Encoding', 'base64'
254
- subject.stream file_object
255
- end
325
+ let(:file_response) do
326
+ file_body = Grape::ServeStream::FileBody.new(file_path)
327
+ Grape::ServeStream::StreamResponse.new(file_body)
328
+ end
256
329
 
257
- it 'returns value wrapped in FileResponse' do
258
- expect(subject.stream).to eq Grape::ServeFile::FileResponse.new(file_object)
259
- end
330
+ before do
331
+ subject.header 'Cache-Control', 'cache'
332
+ subject.header 'Content-Length', 123
333
+ subject.header 'Transfer-Encoding', 'base64'
334
+ end
260
335
 
261
- it 'also sets result of file to value wrapped in FileResponse' do
262
- expect(subject.file).to eq Grape::ServeFile::FileResponse.new(file_object)
263
- end
336
+ it 'emits no deprecation warnings' do
337
+ expect(subject).to_not receive(:warn)
338
+
339
+ subject.stream file_path
340
+ end
341
+
342
+ it 'returns file body wrapped in StreamResponse' do
343
+ subject.stream file_path
344
+
345
+ expect(subject.stream).to eq file_response
346
+ end
347
+
348
+ it 'sets Cache-Control header to no-cache' do
349
+ subject.stream file_path
350
+
351
+ expect(subject.header['Cache-Control']).to eq 'no-cache'
352
+ end
353
+
354
+ it 'does not change Cache-Control header' do
355
+ subject.stream
356
+
357
+ expect(subject.header['Cache-Control']).to eq 'cache'
358
+ end
359
+
360
+ it 'sets Content-Length header to nil' do
361
+ subject.stream file_path
362
+
363
+ expect(subject.header['Content-Length']).to eq nil
364
+ end
365
+
366
+ it 'sets Transfer-Encoding header to nil' do
367
+ subject.stream file_path
264
368
 
265
- it 'sets Cache-Control header to no-cache' do
266
- expect(subject.header['Cache-Control']).to eq 'no-cache'
369
+ expect(subject.header['Transfer-Encoding']).to eq nil
370
+ end
267
371
  end
268
372
 
269
- it 'sets Content-Length header to nil' do
270
- expect(subject.header['Content-Length']).to eq nil
373
+ context 'as a stream object' do
374
+ let(:stream_object) { double('StreamerObject', each: nil) }
375
+
376
+ let(:stream_response) do
377
+ Grape::ServeStream::StreamResponse.new(stream_object)
378
+ end
379
+
380
+ before do
381
+ subject.header 'Cache-Control', 'cache'
382
+ subject.header 'Content-Length', 123
383
+ subject.header 'Transfer-Encoding', 'base64'
384
+ end
385
+
386
+ it 'emits no deprecation warnings' do
387
+ expect(subject).to_not receive(:warn)
388
+
389
+ subject.stream stream_object
390
+ end
391
+
392
+ it 'returns value wrapped in StreamResponse' do
393
+ subject.stream stream_object
394
+
395
+ expect(subject.stream).to eq stream_response
396
+ end
397
+
398
+ it 'sets Cache-Control header to no-cache' do
399
+ subject.stream stream_object
400
+
401
+ expect(subject.header['Cache-Control']).to eq 'no-cache'
402
+ end
403
+
404
+ it 'sets Content-Length header to nil' do
405
+ subject.stream stream_object
406
+
407
+ expect(subject.header['Content-Length']).to eq nil
408
+ end
409
+
410
+ it 'sets Transfer-Encoding header to nil' do
411
+ subject.stream stream_object
412
+
413
+ expect(subject.header['Transfer-Encoding']).to eq nil
414
+ end
271
415
  end
272
416
 
273
- it 'sets Transfer-Encoding header to nil' do
274
- expect(subject.header['Transfer-Encoding']).to eq nil
417
+ context 'as a non-stream object' do
418
+ let(:non_stream_object) { double('NonStreamerObject') }
419
+
420
+ it 'raises an error that the object must implement :each' do
421
+ expect { subject.stream non_stream_object }.to raise_error(ArgumentError, /:each/)
422
+ end
275
423
  end
276
424
  end
277
425
 
278
426
  it 'returns default' do
279
- expect(subject.file).to be nil
427
+ expect(subject.stream).to be nil
428
+ expect(subject.header['Cache-Control']).to eq nil
280
429
  end
281
430
  end
282
431