grape 1.3.3 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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