grape 1.3.1 → 1.5.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +82 -1
- data/LICENSE +1 -1
- data/README.md +104 -21
- data/UPGRADING.md +265 -39
- data/lib/grape.rb +2 -2
- data/lib/grape/api.rb +2 -2
- data/lib/grape/api/instance.rb +29 -28
- data/lib/grape/dsl/helpers.rb +1 -0
- data/lib/grape/dsl/inside_route.rb +69 -36
- data/lib/grape/dsl/parameters.rb +7 -3
- data/lib/grape/dsl/routing.rb +2 -4
- data/lib/grape/dsl/validations.rb +18 -1
- data/lib/grape/eager_load.rb +1 -1
- data/lib/grape/endpoint.rb +8 -6
- data/lib/grape/http/headers.rb +1 -0
- data/lib/grape/middleware/base.rb +2 -1
- data/lib/grape/middleware/error.rb +10 -12
- data/lib/grape/middleware/formatter.rb +3 -3
- data/lib/grape/middleware/stack.rb +21 -8
- data/lib/grape/middleware/versioner/header.rb +1 -1
- data/lib/grape/middleware/versioner/parse_media_type_patch.rb +2 -1
- data/lib/grape/path.rb +2 -2
- data/lib/grape/request.rb +1 -1
- data/lib/grape/router.rb +30 -43
- data/lib/grape/router/attribute_translator.rb +26 -5
- data/lib/grape/router/route.rb +3 -22
- data/lib/grape/{serve_file → serve_stream}/file_body.rb +1 -1
- data/lib/grape/{serve_file → serve_stream}/sendfile_response.rb +1 -1
- data/lib/grape/{serve_file/file_response.rb → serve_stream/stream_response.rb} +8 -8
- data/lib/grape/util/base_inheritable.rb +11 -8
- data/lib/grape/util/lazy_value.rb +1 -0
- data/lib/grape/util/reverse_stackable_values.rb +3 -1
- data/lib/grape/util/stackable_values.rb +3 -1
- data/lib/grape/validations/attributes_iterator.rb +8 -0
- data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
- data/lib/grape/validations/params_scope.rb +8 -6
- data/lib/grape/validations/single_attribute_iterator.rb +1 -1
- data/lib/grape/validations/types.rb +6 -5
- data/lib/grape/validations/types/array_coercer.rb +14 -5
- data/lib/grape/validations/types/build_coercer.rb +5 -8
- data/lib/grape/validations/types/custom_type_coercer.rb +14 -2
- data/lib/grape/validations/types/dry_type_coercer.rb +36 -1
- data/lib/grape/validations/types/file.rb +15 -13
- data/lib/grape/validations/types/json.rb +40 -36
- data/lib/grape/validations/types/primitive_coercer.rb +11 -5
- data/lib/grape/validations/types/set_coercer.rb +6 -4
- data/lib/grape/validations/types/variant_collection_coercer.rb +1 -1
- data/lib/grape/validations/validator_factory.rb +1 -1
- data/lib/grape/validations/validators/as.rb +1 -1
- data/lib/grape/validations/validators/base.rb +4 -5
- data/lib/grape/validations/validators/coerce.rb +3 -10
- data/lib/grape/validations/validators/default.rb +3 -5
- data/lib/grape/validations/validators/except_values.rb +1 -1
- data/lib/grape/validations/validators/multiple_params_base.rb +2 -1
- data/lib/grape/validations/validators/regexp.rb +1 -1
- data/lib/grape/validations/validators/values.rb +1 -1
- data/lib/grape/version.rb +1 -1
- data/spec/grape/api/instance_spec.rb +50 -0
- data/spec/grape/api_spec.rb +75 -0
- data/spec/grape/dsl/inside_route_spec.rb +182 -33
- data/spec/grape/endpoint/declared_spec.rb +601 -0
- data/spec/grape/endpoint_spec.rb +0 -521
- data/spec/grape/entity_spec.rb +6 -0
- data/spec/grape/integration/rack_sendfile_spec.rb +12 -8
- data/spec/grape/middleware/auth/strategies_spec.rb +1 -1
- data/spec/grape/middleware/error_spec.rb +1 -1
- data/spec/grape/middleware/formatter_spec.rb +1 -1
- data/spec/grape/middleware/stack_spec.rb +3 -1
- data/spec/grape/path_spec.rb +4 -4
- data/spec/grape/validations/multiple_attributes_iterator_spec.rb +13 -3
- data/spec/grape/validations/params_scope_spec.rb +26 -0
- data/spec/grape/validations/single_attribute_iterator_spec.rb +17 -6
- data/spec/grape/validations/types/array_coercer_spec.rb +35 -0
- data/spec/grape/validations/types/primitive_coercer_spec.rb +65 -5
- data/spec/grape/validations/types/set_coercer_spec.rb +34 -0
- data/spec/grape/validations/types_spec.rb +1 -1
- data/spec/grape/validations/validators/coerce_spec.rb +317 -29
- data/spec/grape/validations/validators/default_spec.rb +170 -0
- data/spec/grape/validations/validators/except_values_spec.rb +1 -0
- data/spec/grape/validations/validators/values_spec.rb +1 -1
- data/spec/grape/validations_spec.rb +290 -18
- data/spec/integration/eager_load/eager_load_spec.rb +15 -0
- data/spec/spec_helper.rb +0 -10
- data/spec/support/chunks.rb +14 -0
- data/spec/support/versioned_helpers.rb +3 -5
- 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
|
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 '
|
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 <
|
11
|
+
class SetCoercer < ArrayCoercer
|
12
|
+
register_collection Set
|
13
|
+
|
12
14
|
def initialize(type, strict = false)
|
13
15
|
super
|
14
16
|
|
15
|
-
@
|
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 =
|
30
|
+
coerced_elem = elem_coercer.call(elem)
|
29
31
|
|
30
32
|
return coerced_elem if coerced_elem.is_a?(InvalidValue)
|
31
33
|
|
@@ -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]
|
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
|
-
|
26
|
-
|
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
|
|
@@ -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? ||
|
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]
|
data/lib/grape/version.rb
CHANGED
@@ -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
|
data/spec/grape/api_spec.rb
CHANGED
@@ -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
|
-
|
211
|
-
|
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 '
|
220
|
-
expect(subject
|
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) {
|
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::
|
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.
|
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 '
|
236
|
-
expect
|
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.
|
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
|
-
|
322
|
+
context 'as a file path' do
|
323
|
+
let(:file_path) { '/some/file/path' }
|
249
324
|
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
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
|
-
|
258
|
-
|
259
|
-
|
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
|
-
|
262
|
-
|
263
|
-
|
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
|
-
|
266
|
-
|
369
|
+
expect(subject.header['Transfer-Encoding']).to eq nil
|
370
|
+
end
|
267
371
|
end
|
268
372
|
|
269
|
-
|
270
|
-
|
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
|
-
|
274
|
-
|
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.
|
427
|
+
expect(subject.stream).to be nil
|
428
|
+
expect(subject.header['Cache-Control']).to eq nil
|
280
429
|
end
|
281
430
|
end
|
282
431
|
|