grape 1.3.3 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -124,10 +124,10 @@ module Grape
124
124
  def optioned_declared_params(**options)
125
125
  declared_params = if options[:include_parent_namespaces]
126
126
  # Declared params including parent namespaces
127
- route_setting(:saved_declared_params).flatten | Array(route_setting(:declared_params))
127
+ route_setting(:declared_params)
128
128
  else
129
129
  # Declared params at current namespace
130
- route_setting(:saved_declared_params).last & Array(route_setting(:declared_params))
130
+ namespace_stackable(:declared_params).last || []
131
131
  end
132
132
 
133
133
  raise ArgumentError, 'Tried to filter for declared parameters but none exist.' unless declared_params
@@ -279,23 +279,36 @@ module Grape
279
279
  body false
280
280
  end
281
281
 
282
- # Allows you to define the response as a file-like object.
282
+ # Deprecated method to send files to the client. Use `sendfile` or `stream`
283
+ def file(value = nil)
284
+ if value.is_a?(String)
285
+ warn '[DEPRECATION] Use sendfile or stream to send files.'
286
+ sendfile(value)
287
+ elsif !value.is_a?(NilClass)
288
+ warn '[DEPRECATION] Use stream to use a Stream object.'
289
+ stream(value)
290
+ else
291
+ warn '[DEPRECATION] Use sendfile or stream to send files.'
292
+ sendfile
293
+ end
294
+ end
295
+
296
+ # Allows you to send a file to the client via sendfile.
283
297
  #
284
298
  # @example
285
299
  # get '/file' do
286
- # file FileStreamer.new(...)
300
+ # sendfile FileStreamer.new(...)
287
301
  # end
288
302
  #
289
303
  # GET /file # => "contents of file"
290
- def file(value = nil)
304
+ def sendfile(value = nil)
291
305
  if value.is_a?(String)
292
- file_body = Grape::ServeFile::FileBody.new(value)
293
- @file = Grape::ServeFile::FileResponse.new(file_body)
306
+ file_body = Grape::ServeStream::FileBody.new(value)
307
+ @stream = Grape::ServeStream::StreamResponse.new(file_body)
294
308
  elsif !value.is_a?(NilClass)
295
- warn '[DEPRECATION] Argument as FileStreamer-like object is deprecated. Use path to file instead.'
296
- @file = Grape::ServeFile::FileResponse.new(value)
309
+ raise ArgumentError, 'Argument must be a file path'
297
310
  else
298
- instance_variable_defined?(:@file) ? @file : nil
311
+ stream
299
312
  end
300
313
  end
301
314
 
@@ -318,7 +331,16 @@ module Grape
318
331
  header 'Content-Length', nil
319
332
  header 'Transfer-Encoding', nil
320
333
  header 'Cache-Control', 'no-cache' # Skips ETag generation (reading the response up front)
321
- file(value)
334
+ if value.is_a?(String)
335
+ file_body = Grape::ServeStream::FileBody.new(value)
336
+ @stream = Grape::ServeStream::StreamResponse.new(file_body)
337
+ elsif value.respond_to?(:each)
338
+ @stream = Grape::ServeStream::StreamResponse.new(value)
339
+ elsif !value.is_a?(NilClass)
340
+ raise ArgumentError, 'Stream object must respond to :each.'
341
+ else
342
+ instance_variable_defined?(:@stream) ? @stream : nil
343
+ end
322
344
  end
323
345
 
324
346
  # Allows you to make use of Grape Entities by setting
@@ -10,7 +10,24 @@ module Grape
10
10
  include Grape::DSL::Configuration
11
11
 
12
12
  module ClassMethods
13
- # Clears all defined parameters and validations.
13
+ # Clears all defined parameters and validations. The main purpose of it is to clean up
14
+ # settings, so next endpoint won't interfere with previous one.
15
+ #
16
+ # params do
17
+ # # params for the endpoint below this block
18
+ # end
19
+ # post '/current' do
20
+ # # whatever
21
+ # end
22
+ #
23
+ # # somewhere between them the reset_validations! method gets called
24
+ #
25
+ # params do
26
+ # # params for the endpoint below this block
27
+ # end
28
+ # post '/next' do
29
+ # # whatever
30
+ # end
14
31
  def reset_validations!
15
32
  unset_namespace_stackable :declared_params
16
33
  unset_namespace_stackable :validations
@@ -16,5 +16,5 @@ Grape::Parser.eager_load!
16
16
  Grape::DSL.eager_load!
17
17
  Grape::API.eager_load!
18
18
  Grape::Presenters.eager_load!
19
- Grape::ServeFile.eager_load!
19
+ Grape::ServeStream.eager_load!
20
20
  Rack::Head # AutoLoads the Rack::Head
@@ -80,7 +80,10 @@ module Grape
80
80
 
81
81
  self.inheritable_setting = new_settings.point_in_time_copy
82
82
 
83
- route_setting(:saved_declared_params, namespace_stackable(:declared_params))
83
+ # now +namespace_stackable(:declared_params)+ contains all params defined for
84
+ # this endpoint and its parents, but later it will be cleaned up,
85
+ # see +reset_validations!+ in lib/grape/dsl/validations.rb
86
+ route_setting(:declared_params, namespace_stackable(:declared_params).flatten)
84
87
  route_setting(:saved_validations, namespace_stackable(:validations))
85
88
 
86
89
  namespace_stackable(:representations, []) unless namespace_stackable(:representations)
@@ -99,7 +102,7 @@ module Grape
99
102
  @block = nil
100
103
 
101
104
  @status = nil
102
- @file = nil
105
+ @stream = nil
103
106
  @body = nil
104
107
  @proc = nil
105
108
 
@@ -116,7 +119,6 @@ module Grape
116
119
  parent_declared_params = namespace_stackable[:declared_params]
117
120
 
118
121
  if parent_declared_params
119
- inheritable_setting.route[:declared_params] ||= []
120
122
  inheritable_setting.route[:declared_params].concat(parent_declared_params.flatten)
121
123
  end
122
124
 
@@ -190,7 +192,7 @@ module Grape
190
192
  requirements: prepare_routes_requirements,
191
193
  prefix: namespace_inheritable(:root_prefix),
192
194
  anchor: options[:route_options].fetch(:anchor, true),
193
- settings: inheritable_setting.route.except(:saved_declared_params, :saved_validations),
195
+ settings: inheritable_setting.route.except(:declared_params, :saved_validations),
194
196
  forward_match: options[:forward_match]
195
197
  }
196
198
  end
@@ -271,8 +273,8 @@ module Grape
271
273
  # status verifies body presence when DELETE
272
274
  @body ||= response_object
273
275
 
274
- # The body commonly is an Array of Strings, the application instance itself, or a File-like object
275
- response_object = file || [body]
276
+ # The body commonly is an Array of Strings, the application instance itself, or a Stream-like object
277
+ response_object = stream || [body]
276
278
 
277
279
  [status, header, response_object]
278
280
  ensure
@@ -14,9 +14,9 @@ module Grape
14
14
 
15
15
  # @param [Rack Application] app The standard argument for a Rack middleware.
16
16
  # @param [Hash] options A hash of options, simply stored for use by subclasses.
17
- def initialize(app, **options)
17
+ def initialize(app, options = {})
18
18
  @app = app
19
- @options = default_options.merge(**options)
19
+ @options = default_options.merge(options)
20
20
  @app_response = nil
21
21
  end
22
22
 
@@ -36,9 +36,9 @@ module Grape
36
36
  def build_formatted_response(status, headers, bodies)
37
37
  headers = ensure_content_type(headers)
38
38
 
39
- if bodies.is_a?(Grape::ServeFile::FileResponse)
40
- Grape::ServeFile::SendfileResponse.new([], status, headers) do |resp|
41
- resp.body = bodies.file
39
+ if bodies.is_a?(Grape::ServeStream::StreamResponse)
40
+ Grape::ServeStream::SendfileResponse.new([], status, headers) do |resp|
41
+ resp.body = bodies.stream
42
42
  end
43
43
  else
44
44
  # Allow content-type to be explicitly overwritten
@@ -7,20 +7,12 @@ module Grape
7
7
  class Router
8
8
  attr_reader :map, :compiled
9
9
 
10
- class NormalizePathCache < Grape::Util::Cache
11
- def initialize
12
- @cache = Hash.new do |h, path|
13
- normalized_path = +"/#{path}"
14
- normalized_path.squeeze!('/')
15
- normalized_path.sub!(%r{/+\Z}, '')
16
- normalized_path = '/' if normalized_path.empty?
17
- h[path] = -normalized_path
18
- end
19
- end
20
- end
21
-
22
10
  def self.normalize_path(path)
23
- NormalizePathCache[path]
11
+ path = +"/#{path}"
12
+ path.squeeze!('/')
13
+ path.sub!(%r{/+\Z}, '')
14
+ path = '/' if path == ''
15
+ path
24
16
  end
25
17
 
26
18
  def self.supported_methods
@@ -99,37 +91,34 @@ module Grape
99
91
  response = yield(input, method)
100
92
 
101
93
  return response if response && !(cascade = cascade?(response))
102
- neighbor = greedy_match?(input)
94
+ last_neighbor_route = greedy_match?(input)
103
95
 
104
- # If neighbor exists and request method is OPTIONS,
96
+ # If last_neighbor_route exists and request method is OPTIONS,
105
97
  # return response by using #call_with_allow_headers.
106
- return call_with_allow_headers(
107
- env,
108
- neighbor.allow_header,
109
- neighbor.endpoint
110
- ) if neighbor && method == Grape::Http::Headers::OPTIONS && !cascade
98
+ return call_with_allow_headers(env, last_neighbor_route) if last_neighbor_route && method == Grape::Http::Headers::OPTIONS && !cascade
111
99
 
112
100
  route = match?(input, '*')
113
- return neighbor.endpoint.call(env) if neighbor && cascade && route
101
+
102
+ return last_neighbor_route.endpoint.call(env) if last_neighbor_route && cascade && route
114
103
 
115
104
  if route
116
105
  response = process_route(route, env)
117
106
  return response if response && !(cascade = cascade?(response))
118
107
  end
119
108
 
120
- !cascade && neighbor ? call_with_allow_headers(env, neighbor.allow_header, neighbor.endpoint) : nil
109
+ return call_with_allow_headers(env, last_neighbor_route) if !cascade && last_neighbor_route
110
+
111
+ nil
121
112
  end
122
113
 
123
114
  def process_route(route, env)
124
- input, = *extract_input_and_method(env)
125
- routing_args = env[Grape::Env::GRAPE_ROUTING_ARGS]
126
- env[Grape::Env::GRAPE_ROUTING_ARGS] = make_routing_args(routing_args, route, input)
115
+ prepare_env_from_route(env, route)
127
116
  route.exec(env)
128
117
  end
129
118
 
130
119
  def make_routing_args(default_args, route, input)
131
120
  args = default_args || { route_info: route }
132
- args.merge(route.params(input))
121
+ args.merge(route.params(input) || {})
133
122
  end
134
123
 
135
124
  def extract_input_and_method(env)
@@ -160,9 +149,15 @@ module Grape
160
149
  @neutral_map.detect { |route| last_match["_#{route.index}"] }
161
150
  end
162
151
 
163
- def call_with_allow_headers(env, methods, endpoint)
164
- env[Grape::Env::GRAPE_ALLOWED_METHODS] = methods.join(', ').freeze
165
- endpoint.call(env)
152
+ def call_with_allow_headers(env, route)
153
+ prepare_env_from_route(env, route)
154
+ env[Grape::Env::GRAPE_ALLOWED_METHODS] = route.allow_header.join(', ').freeze
155
+ route.endpoint.call(env)
156
+ end
157
+
158
+ def prepare_env_from_route(env, route)
159
+ input, = *extract_input_and_method(env)
160
+ env[Grape::Env::GRAPE_ROUTING_ARGS] = make_routing_args(env[Grape::Env::GRAPE_ROUTING_ARGS], route, input)
166
161
  end
167
162
 
168
163
  def cascade?(response)
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Grape
4
- module ServeFile
4
+ module ServeStream
5
5
  CHUNK_SIZE = 16_384
6
6
 
7
7
  # Class helps send file through API
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Grape
4
- module ServeFile
4
+ module ServeStream
5
5
  # Response should respond to to_path method
6
6
  # for using Rack::SendFile middleware
7
7
  class SendfileResponse < Rack::Response
@@ -1,22 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Grape
4
- module ServeFile
5
- # A simple class used to identify responses which represent files and do not
4
+ module ServeStream
5
+ # A simple class used to identify responses which represent streams (or files) and do not
6
6
  # need to be formatted or pre-read by Rack::Response
7
- class FileResponse
8
- attr_reader :file
7
+ class StreamResponse
8
+ attr_reader :stream
9
9
 
10
- # @param file [Object]
11
- def initialize(file)
12
- @file = file
10
+ # @param stream [Object]
11
+ def initialize(stream)
12
+ @stream = stream
13
13
  end
14
14
 
15
15
  # Equality provided mostly for tests.
16
16
  #
17
17
  # @return [Boolean]
18
18
  def ==(other)
19
- file == other.file
19
+ stream == other.stream
20
20
  end
21
21
  end
22
22
  end
@@ -237,10 +237,10 @@ module Grape
237
237
  @parent.push_declared_params [element => @declared_params]
238
238
  else
239
239
  @api.namespace_stackable(:declared_params, @declared_params)
240
-
241
- @api.route_setting(:declared_params, []) unless @api.route_setting(:declared_params)
242
- @api.route_setting(:declared_params, @api.namespace_stackable(:declared_params).flatten)
243
240
  end
241
+
242
+ # params were stored in settings, it can be cleaned from the params scope
243
+ @declared_params = nil
244
244
  end
245
245
 
246
246
  def validates(attrs, validations)
@@ -37,7 +37,6 @@ module Grape
37
37
  def call(val)
38
38
  return InvalidValue.new if reject?(val)
39
39
  return nil if val.nil? || treat_as_nil?(val)
40
- return '' if val == ''
41
40
 
42
41
  super
43
42
  end
@@ -60,7 +59,7 @@ module Grape
60
59
  # absence of a value and coerces it into nil. See a discussion there
61
60
  # https://github.com/ruby-grape/grape/pull/2045
62
61
  def treat_as_nil?(val)
63
- val == '' && type == Grape::API::Boolean
62
+ val == '' && type != String
64
63
  end
65
64
  end
66
65
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Grape
4
4
  # The current version of Grape.
5
- VERSION = '1.3.3'
5
+ VERSION = '1.4.0'
6
6
  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
@@ -203,80 +203,222 @@ 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 'sets Content-Length header to nil' do
355
+ subject.stream file_path
356
+
357
+ expect(subject.header['Content-Length']).to eq nil
358
+ end
359
+
360
+ it 'sets Transfer-Encoding header to nil' do
361
+ subject.stream file_path
264
362
 
265
- it 'sets Cache-Control header to no-cache' do
266
- expect(subject.header['Cache-Control']).to eq 'no-cache'
363
+ expect(subject.header['Transfer-Encoding']).to eq nil
364
+ end
267
365
  end
268
366
 
269
- it 'sets Content-Length header to nil' do
270
- expect(subject.header['Content-Length']).to eq nil
367
+ context 'as a stream object' do
368
+ let(:stream_object) { double('StreamerObject', each: nil) }
369
+
370
+ let(:stream_response) do
371
+ Grape::ServeStream::StreamResponse.new(stream_object)
372
+ end
373
+
374
+ before do
375
+ subject.header 'Cache-Control', 'cache'
376
+ subject.header 'Content-Length', 123
377
+ subject.header 'Transfer-Encoding', 'base64'
378
+ end
379
+
380
+ it 'emits no deprecation warnings' do
381
+ expect(subject).to_not receive(:warn)
382
+
383
+ subject.stream stream_object
384
+ end
385
+
386
+ it 'returns value wrapped in StreamResponse' do
387
+ subject.stream stream_object
388
+
389
+ expect(subject.stream).to eq stream_response
390
+ end
391
+
392
+ it 'sets Cache-Control header to no-cache' do
393
+ subject.stream stream_object
394
+
395
+ expect(subject.header['Cache-Control']).to eq 'no-cache'
396
+ end
397
+
398
+ it 'sets Content-Length header to nil' do
399
+ subject.stream stream_object
400
+
401
+ expect(subject.header['Content-Length']).to eq nil
402
+ end
403
+
404
+ it 'sets Transfer-Encoding header to nil' do
405
+ subject.stream stream_object
406
+
407
+ expect(subject.header['Transfer-Encoding']).to eq nil
408
+ end
271
409
  end
272
410
 
273
- it 'sets Transfer-Encoding header to nil' do
274
- expect(subject.header['Transfer-Encoding']).to eq nil
411
+ context 'as a non-stream object' do
412
+ let(:non_stream_object) { double('NonStreamerObject') }
413
+
414
+ it 'raises an error that the object must implement :each' do
415
+ expect { subject.stream non_stream_object }.to raise_error(ArgumentError, /:each/)
416
+ end
275
417
  end
276
418
  end
277
419
 
278
420
  it 'returns default' do
279
- expect(subject.file).to be nil
421
+ expect(subject.stream).to be nil
280
422
  end
281
423
  end
282
424