grape 0.9.0 → 0.10.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.
Potentially problematic release.
This version of grape might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -66
- data/.rubocop_todo.yml +78 -17
- data/.travis.yml +7 -3
- data/Appraisals +7 -0
- data/CHANGELOG.md +24 -0
- data/CONTRIBUTING.md +7 -0
- data/Gemfile +1 -7
- data/Guardfile +1 -1
- data/README.md +560 -94
- data/RELEASING.md +1 -1
- data/Rakefile +10 -11
- data/UPGRADING.md +211 -3
- data/gemfiles/rails_3.gemfile +14 -0
- data/gemfiles/rails_4.gemfile +14 -0
- data/grape.gemspec +10 -9
- data/lib/backports/active_support/deep_dup.rb +49 -0
- data/lib/backports/active_support/duplicable.rb +88 -0
- data/lib/grape.rb +29 -2
- data/lib/grape/api.rb +59 -65
- data/lib/grape/dsl/api.rb +19 -0
- data/lib/grape/dsl/callbacks.rb +6 -4
- data/lib/grape/dsl/configuration.rb +49 -5
- data/lib/grape/dsl/helpers.rb +7 -8
- data/lib/grape/dsl/inside_route.rb +22 -10
- data/lib/grape/dsl/middleware.rb +5 -5
- data/lib/grape/dsl/parameters.rb +6 -2
- data/lib/grape/dsl/request_response.rb +23 -20
- data/lib/grape/dsl/routing.rb +52 -49
- data/lib/grape/dsl/settings.rb +110 -0
- data/lib/grape/dsl/validations.rb +14 -6
- data/lib/grape/endpoint.rb +104 -88
- data/lib/grape/exceptions/base.rb +2 -2
- data/lib/grape/exceptions/incompatible_option_values.rb +1 -1
- data/lib/grape/exceptions/invalid_formatter.rb +1 -1
- data/lib/grape/exceptions/invalid_versioner_option.rb +1 -1
- data/lib/grape/exceptions/invalid_with_option_for_represent.rb +1 -1
- data/lib/grape/exceptions/missing_mime_type.rb +1 -1
- data/lib/grape/exceptions/missing_option.rb +1 -1
- data/lib/grape/exceptions/missing_vendor_option.rb +1 -1
- data/lib/grape/exceptions/unknown_options.rb +1 -1
- data/lib/grape/exceptions/unknown_validator.rb +1 -1
- data/lib/grape/exceptions/validation.rb +1 -1
- data/lib/grape/exceptions/validation_errors.rb +2 -2
- data/lib/grape/formatter/serializable_hash.rb +1 -1
- data/lib/grape/formatter/xml.rb +1 -1
- data/lib/grape/locale/en.yml +2 -0
- data/lib/grape/middleware/auth/dsl.rb +26 -21
- data/lib/grape/middleware/auth/strategies.rb +1 -1
- data/lib/grape/middleware/auth/strategy_info.rb +0 -2
- data/lib/grape/middleware/base.rb +2 -2
- data/lib/grape/middleware/error.rb +1 -1
- data/lib/grape/middleware/formatter.rb +5 -5
- data/lib/grape/middleware/versioner.rb +1 -1
- data/lib/grape/middleware/versioner/header.rb +3 -3
- data/lib/grape/middleware/versioner/param.rb +2 -2
- data/lib/grape/middleware/versioner/path.rb +1 -1
- data/lib/grape/namespace.rb +1 -1
- data/lib/grape/path.rb +9 -3
- data/lib/grape/util/content_types.rb +16 -8
- data/lib/grape/util/inheritable_setting.rb +74 -0
- data/lib/grape/util/inheritable_values.rb +51 -0
- data/lib/grape/util/stackable_values.rb +52 -0
- data/lib/grape/util/strict_hash_configuration.rb +106 -0
- data/lib/grape/validations.rb +0 -220
- data/lib/grape/validations/attributes_iterator.rb +21 -0
- data/lib/grape/validations/params_scope.rb +176 -0
- data/lib/grape/validations/validators/all_or_none.rb +20 -0
- data/lib/grape/validations/validators/allow_blank.rb +30 -0
- data/lib/grape/validations/validators/at_least_one_of.rb +20 -0
- data/lib/grape/validations/validators/base.rb +37 -0
- data/lib/grape/validations/{coerce.rb → validators/coerce.rb} +3 -3
- data/lib/grape/validations/{default.rb → validators/default.rb} +1 -1
- data/lib/grape/validations/validators/exactly_one_of.rb +20 -0
- data/lib/grape/validations/validators/multiple_params_base.rb +26 -0
- data/lib/grape/validations/validators/mutual_exclusion.rb +25 -0
- data/lib/grape/validations/{presence.rb → validators/presence.rb} +2 -2
- data/lib/grape/validations/validators/regexp.rb +12 -0
- data/lib/grape/validations/validators/values.rb +26 -0
- data/lib/grape/version.rb +1 -1
- data/spec/grape/api_spec.rb +522 -343
- data/spec/grape/dsl/callbacks_spec.rb +4 -4
- data/spec/grape/dsl/configuration_spec.rb +48 -9
- data/spec/grape/dsl/helpers_spec.rb +6 -13
- data/spec/grape/dsl/inside_route_spec.rb +43 -4
- data/spec/grape/dsl/middleware_spec.rb +1 -10
- data/spec/grape/dsl/parameters_spec.rb +8 -1
- data/spec/grape/dsl/request_response_spec.rb +16 -22
- data/spec/grape/dsl/routing_spec.rb +21 -5
- data/spec/grape/dsl/settings_spec.rb +219 -0
- data/spec/grape/dsl/validations_spec.rb +8 -11
- data/spec/grape/endpoint_spec.rb +115 -86
- data/spec/grape/entity_spec.rb +33 -33
- data/spec/grape/exceptions/invalid_formatter_spec.rb +3 -5
- data/spec/grape/exceptions/invalid_versioner_option_spec.rb +4 -6
- data/spec/grape/exceptions/missing_mime_type_spec.rb +5 -6
- data/spec/grape/exceptions/missing_option_spec.rb +3 -5
- data/spec/grape/exceptions/unknown_options_spec.rb +3 -5
- data/spec/grape/exceptions/unknown_validator_spec.rb +3 -5
- data/spec/grape/exceptions/validation_errors_spec.rb +5 -5
- data/spec/grape/loading_spec.rb +44 -0
- data/spec/grape/middleware/auth/base_spec.rb +0 -4
- data/spec/grape/middleware/auth/dsl_spec.rb +2 -4
- data/spec/grape/middleware/auth/strategies_spec.rb +5 -6
- data/spec/grape/middleware/exception_spec.rb +8 -10
- data/spec/grape/middleware/formatter_spec.rb +13 -15
- data/spec/grape/middleware/versioner/accept_version_header_spec.rb +10 -10
- data/spec/grape/middleware/versioner/header_spec.rb +25 -25
- data/spec/grape/middleware/versioner/param_spec.rb +15 -17
- data/spec/grape/middleware/versioner/path_spec.rb +1 -2
- data/spec/grape/middleware/versioner_spec.rb +0 -1
- data/spec/grape/path_spec.rb +66 -45
- data/spec/grape/util/inheritable_setting_spec.rb +217 -0
- data/spec/grape/util/inheritable_values_spec.rb +63 -0
- data/spec/grape/util/stackable_values_spec.rb +115 -0
- data/spec/grape/util/strict_hash_configuration_spec.rb +38 -0
- data/spec/grape/validations/attributes_iterator_spec.rb +4 -0
- data/spec/grape/validations/params_scope_spec.rb +57 -0
- data/spec/grape/validations/validators/all_or_none_spec.rb +60 -0
- data/spec/grape/validations/validators/allow_blank_spec.rb +170 -0
- data/spec/grape/validations/{at_least_one_of_spec.rb → validators/at_least_one_of_spec.rb} +7 -3
- data/spec/grape/validations/{coerce_spec.rb → validators/coerce_spec.rb} +8 -11
- data/spec/grape/validations/{default_spec.rb → validators/default_spec.rb} +7 -9
- data/spec/grape/validations/{exactly_one_of_spec.rb → validators/exactly_one_of_spec.rb} +15 -11
- data/spec/grape/validations/{mutual_exclusion_spec.rb → validators/mutual_exclusion_spec.rb} +11 -9
- data/spec/grape/validations/{presence_spec.rb → validators/presence_spec.rb} +30 -30
- data/spec/grape/validations/{regexp_spec.rb → validators/regexp_spec.rb} +2 -4
- data/spec/grape/validations/{values_spec.rb → validators/values_spec.rb} +95 -23
- data/spec/grape/validations/{zh-CN.yml → validators/zh-CN.yml} +0 -0
- data/spec/grape/validations_spec.rb +335 -70
- data/spec/shared/versioning_examples.rb +7 -8
- data/spec/spec_helper.rb +2 -0
- data/spec/support/basic_auth_encode_helpers.rb +1 -1
- data/spec/support/content_type_helpers.rb +1 -1
- data/spec/support/versioned_helpers.rb +3 -3
- metadata +80 -33
- data/lib/grape/util/deep_merge.rb +0 -23
- data/lib/grape/util/hash_stack.rb +0 -120
- data/lib/grape/validations/at_least_one_of.rb +0 -25
- data/lib/grape/validations/exactly_one_of.rb +0 -26
- data/lib/grape/validations/mutual_exclusion.rb +0 -25
- data/lib/grape/validations/regexp.rb +0 -12
- data/lib/grape/validations/values.rb +0 -23
- data/spec/grape/util/hash_stack_spec.rb +0 -132
@@ -0,0 +1,20 @@
|
|
1
|
+
module Grape
|
2
|
+
module Validations
|
3
|
+
require 'grape/validations/validators/multiple_params_base'
|
4
|
+
class AllOrNoneOfValidator < MultipleParamsBase
|
5
|
+
def validate!(params)
|
6
|
+
super
|
7
|
+
if scope_requires_params && only_subset_present
|
8
|
+
fail Grape::Exceptions::Validation, params: all_keys, message_key: :all_or_none
|
9
|
+
end
|
10
|
+
params
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def only_subset_present
|
16
|
+
scoped_params.any? { |resource_params| keys_in_common(resource_params).length > 0 && keys_in_common(resource_params).length < attrs.length }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Grape
|
2
|
+
module Validations
|
3
|
+
class AllowBlankValidator < Base
|
4
|
+
def validate_param!(attr_name, params)
|
5
|
+
return if @option || !params.is_a?(Hash)
|
6
|
+
|
7
|
+
value = params[attr_name]
|
8
|
+
value = value.strip if value.respond_to?(:strip)
|
9
|
+
|
10
|
+
key_exists = params.key?(attr_name)
|
11
|
+
|
12
|
+
if @scope.root?
|
13
|
+
# root scope. validate if it's a required param. if it's optional, validate only if key exists in hash
|
14
|
+
should_validate = @required || key_exists
|
15
|
+
else # nested scope
|
16
|
+
should_validate = # required param, and scope contains some values (if scoping element contains no values, treat as blank)
|
17
|
+
(@required && params.present?) ||
|
18
|
+
# optional param but key inside scoping element exists
|
19
|
+
(!@required && params.key?(attr_name))
|
20
|
+
end
|
21
|
+
|
22
|
+
return unless should_validate
|
23
|
+
|
24
|
+
unless value.present?
|
25
|
+
fail Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message_key: :blank
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Grape
|
2
|
+
module Validations
|
3
|
+
require 'grape/validations/validators/multiple_params_base'
|
4
|
+
class AtLeastOneOfValidator < MultipleParamsBase
|
5
|
+
def validate!(params)
|
6
|
+
super
|
7
|
+
if scope_requires_params && no_exclusive_params_are_present
|
8
|
+
fail Grape::Exceptions::Validation, params: all_keys, message_key: :at_least_one
|
9
|
+
end
|
10
|
+
params
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def no_exclusive_params_are_present
|
16
|
+
scoped_params.any? { |resource_params| keys_in_common(resource_params).empty? }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Grape
|
2
|
+
module Validations
|
3
|
+
class Base
|
4
|
+
attr_reader :attrs
|
5
|
+
|
6
|
+
def initialize(attrs, options, required, scope)
|
7
|
+
@attrs = Array(attrs)
|
8
|
+
@option = options
|
9
|
+
@required = required
|
10
|
+
@scope = scope
|
11
|
+
end
|
12
|
+
|
13
|
+
def validate!(params)
|
14
|
+
attributes = AttributesIterator.new(self, @scope, params)
|
15
|
+
attributes.each do |resource_params, attr_name|
|
16
|
+
if @required || (resource_params.respond_to?(:key?) && resource_params.key?(attr_name))
|
17
|
+
validate_param!(attr_name, resource_params)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.convert_to_short_name(klass)
|
23
|
+
ret = klass.name.gsub(/::/, '/')
|
24
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
25
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
26
|
+
.tr('-', '_')
|
27
|
+
.downcase
|
28
|
+
File.basename(ret, '_validator')
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.inherited(klass)
|
32
|
+
short_name = convert_to_short_name(klass)
|
33
|
+
Validations.register_validator(short_name, klass)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -4,14 +4,14 @@ module Grape
|
|
4
4
|
end
|
5
5
|
|
6
6
|
module Validations
|
7
|
-
class CoerceValidator <
|
7
|
+
class CoerceValidator < Base
|
8
8
|
def validate_param!(attr_name, params)
|
9
|
-
|
9
|
+
fail Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message_key: :coerce unless params.is_a? Hash
|
10
10
|
new_value = coerce_value(@option, params[attr_name])
|
11
11
|
if valid_type?(new_value)
|
12
12
|
params[attr_name] = new_value
|
13
13
|
else
|
14
|
-
|
14
|
+
fail Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message_key: :coerce
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Grape
|
2
|
+
module Validations
|
3
|
+
require 'grape/validations/validators/mutual_exclusion'
|
4
|
+
class ExactlyOneOfValidator < MutualExclusionValidator
|
5
|
+
def validate!(params)
|
6
|
+
super
|
7
|
+
if scope_requires_params && none_of_restricted_params_is_present
|
8
|
+
fail Grape::Exceptions::Validation, params: all_keys, message_key: :exactly_one
|
9
|
+
end
|
10
|
+
params
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def none_of_restricted_params_is_present
|
16
|
+
scoped_params.any? { |resource_params| keys_in_common(resource_params).empty? }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Grape
|
2
|
+
module Validations
|
3
|
+
class MultipleParamsBase < Base
|
4
|
+
attr_reader :scoped_params
|
5
|
+
|
6
|
+
def validate!(params)
|
7
|
+
@scoped_params = [@scope.params(params)].flatten
|
8
|
+
params
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def scope_requires_params
|
14
|
+
@scope.required? || scoped_params.any?(&:any?)
|
15
|
+
end
|
16
|
+
|
17
|
+
def keys_in_common(resource_params)
|
18
|
+
(all_keys & resource_params.stringify_keys.keys).map(&:to_s)
|
19
|
+
end
|
20
|
+
|
21
|
+
def all_keys
|
22
|
+
attrs.map(&:to_s)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Grape
|
2
|
+
module Validations
|
3
|
+
require 'grape/validations/validators/multiple_params_base'
|
4
|
+
class MutualExclusionValidator < MultipleParamsBase
|
5
|
+
attr_reader :processing_keys_in_common
|
6
|
+
|
7
|
+
def validate!(params)
|
8
|
+
super
|
9
|
+
if two_or_more_exclusive_params_are_present
|
10
|
+
fail Grape::Exceptions::Validation, params: processing_keys_in_common, message_key: :mutual_exclusion
|
11
|
+
end
|
12
|
+
params
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def two_or_more_exclusive_params_are_present
|
18
|
+
scoped_params.any? do |resource_params|
|
19
|
+
@processing_keys_in_common = keys_in_common(resource_params)
|
20
|
+
@processing_keys_in_common.length > 1
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module Grape
|
2
2
|
module Validations
|
3
|
-
class PresenceValidator <
|
3
|
+
class PresenceValidator < Base
|
4
4
|
def validate!(params)
|
5
5
|
return unless @scope.should_validate?(params)
|
6
6
|
super
|
@@ -8,7 +8,7 @@ module Grape
|
|
8
8
|
|
9
9
|
def validate_param!(attr_name, params)
|
10
10
|
unless params.respond_to?(:key?) && params.key?(attr_name)
|
11
|
-
|
11
|
+
fail Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message_key: :presence
|
12
12
|
end
|
13
13
|
end
|
14
14
|
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Grape
|
2
|
+
module Validations
|
3
|
+
class RegexpValidator < Base
|
4
|
+
def validate_param!(attr_name, params)
|
5
|
+
if params.key?(attr_name) &&
|
6
|
+
(params[attr_name].nil? || !(params[attr_name].to_s =~ @option))
|
7
|
+
fail Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message_key: :regexp
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Grape
|
2
|
+
module Validations
|
3
|
+
class ValuesValidator < Base
|
4
|
+
def initialize(attrs, options, required, scope)
|
5
|
+
@values = options
|
6
|
+
super
|
7
|
+
end
|
8
|
+
|
9
|
+
def validate_param!(attr_name, params)
|
10
|
+
return unless params[attr_name] || required_for_root_scope?
|
11
|
+
|
12
|
+
values = @values.is_a?(Proc) ? @values.call : @values
|
13
|
+
param_array = params[attr_name].nil? ? [nil] : Array.wrap(params[attr_name])
|
14
|
+
unless (param_array - values).empty?
|
15
|
+
fail Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message_key: :values
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def required_for_root_scope?
|
22
|
+
@required && @scope.root?
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/grape/version.rb
CHANGED
data/spec/grape/api_spec.rb
CHANGED
@@ -13,25 +13,45 @@ describe Grape::API do
|
|
13
13
|
it 'routes root through with the prefix' do
|
14
14
|
subject.prefix 'awesome/sauce'
|
15
15
|
subject.get do
|
16
|
-
|
16
|
+
'Hello there.'
|
17
17
|
end
|
18
18
|
|
19
19
|
get 'awesome/sauce/'
|
20
|
-
expect(last_response.
|
20
|
+
expect(last_response.status).to eql 200
|
21
|
+
expect(last_response.body).to eql 'Hello there.'
|
21
22
|
end
|
22
23
|
|
23
24
|
it 'routes through with the prefix' do
|
24
25
|
subject.prefix 'awesome/sauce'
|
25
26
|
subject.get :hello do
|
26
|
-
|
27
|
+
'Hello there.'
|
27
28
|
end
|
28
29
|
|
29
30
|
get 'awesome/sauce/hello'
|
30
|
-
expect(last_response.body).to eql
|
31
|
+
expect(last_response.body).to eql 'Hello there.'
|
31
32
|
|
32
33
|
get '/hello'
|
33
34
|
expect(last_response.status).to eql 404
|
34
35
|
end
|
36
|
+
|
37
|
+
it 'supports OPTIONS' do
|
38
|
+
subject.prefix 'awesome/sauce'
|
39
|
+
subject.get do
|
40
|
+
'Hello there.'
|
41
|
+
end
|
42
|
+
|
43
|
+
options 'awesome/sauce'
|
44
|
+
expect(last_response.status).to eql 204
|
45
|
+
expect(last_response.body).to be_blank
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'disallows POST' do
|
49
|
+
subject.prefix 'awesome/sauce'
|
50
|
+
subject.get
|
51
|
+
|
52
|
+
post 'awesome/sauce'
|
53
|
+
expect(last_response.status).to eql 405
|
54
|
+
end
|
35
55
|
end
|
36
56
|
|
37
57
|
describe '.version' do
|
@@ -64,7 +84,7 @@ describe Grape::API do
|
|
64
84
|
let(:macro_options) do
|
65
85
|
{
|
66
86
|
using: :param,
|
67
|
-
parameter:
|
87
|
+
parameter: 'apiver'
|
68
88
|
}
|
69
89
|
end
|
70
90
|
end
|
@@ -116,7 +136,7 @@ describe Grape::API do
|
|
116
136
|
it 'adds the association to the :representations setting' do
|
117
137
|
klass = Class.new
|
118
138
|
subject.represent Object, with: klass
|
119
|
-
expect(subject.
|
139
|
+
expect(Grape::DSL::Configuration.stacked_hash_to_hash(subject.namespace_stackable(:representations))[Object]).to eq(klass)
|
120
140
|
end
|
121
141
|
end
|
122
142
|
|
@@ -126,7 +146,7 @@ describe Grape::API do
|
|
126
146
|
subject.namespace :awesome do
|
127
147
|
internal_namespace = namespace
|
128
148
|
end
|
129
|
-
expect(internal_namespace).to eql(
|
149
|
+
expect(internal_namespace).to eql('/awesome')
|
130
150
|
end
|
131
151
|
|
132
152
|
it 'comes after the prefix and version' do
|
@@ -134,11 +154,11 @@ describe Grape::API do
|
|
134
154
|
subject.version 'v1', using: :path
|
135
155
|
|
136
156
|
subject.namespace :awesome do
|
137
|
-
get('/hello') {
|
157
|
+
get('/hello') { 'worked' }
|
138
158
|
end
|
139
159
|
|
140
|
-
get
|
141
|
-
expect(last_response.body).to eq(
|
160
|
+
get '/rad/v1/awesome/hello'
|
161
|
+
expect(last_response.body).to eq('worked')
|
142
162
|
end
|
143
163
|
|
144
164
|
it 'cancels itself after the block is over' do
|
@@ -146,7 +166,7 @@ describe Grape::API do
|
|
146
166
|
subject.namespace :awesome do
|
147
167
|
internal_namespace = namespace
|
148
168
|
end
|
149
|
-
expect(subject.namespace).to eql(
|
169
|
+
expect(subject.namespace).to eql('/')
|
150
170
|
end
|
151
171
|
|
152
172
|
it 'is stackable' do
|
@@ -173,21 +193,21 @@ describe Grape::API do
|
|
173
193
|
end
|
174
194
|
end
|
175
195
|
get '/members/23'
|
176
|
-
expect(last_response.body).to eq(
|
196
|
+
expect(last_response.body).to eq('23')
|
177
197
|
expect(inner_namespace).to eq('/members/:member_id')
|
178
198
|
end
|
179
199
|
|
180
200
|
it 'is callable with nil just to push onto the stack' do
|
181
201
|
subject.namespace do
|
182
202
|
version 'v2', using: :path
|
183
|
-
get('/hello') {
|
203
|
+
get('/hello') { 'inner' }
|
184
204
|
end
|
185
|
-
subject.get('/hello') {
|
205
|
+
subject.get('/hello') { 'outer' }
|
186
206
|
|
187
207
|
get '/v2/hello'
|
188
|
-
expect(last_response.body).to eq(
|
208
|
+
expect(last_response.body).to eq('inner')
|
189
209
|
get '/hello'
|
190
|
-
expect(last_response.body).to eq(
|
210
|
+
expect(last_response.body).to eq('outer')
|
191
211
|
end
|
192
212
|
|
193
213
|
%w(group resource resources segment).each do |als|
|
@@ -196,7 +216,7 @@ describe Grape::API do
|
|
196
216
|
subject.send(als, :awesome) do
|
197
217
|
inner_namespace = namespace
|
198
218
|
end
|
199
|
-
expect(inner_namespace).to eq
|
219
|
+
expect(inner_namespace).to eq '/awesome'
|
200
220
|
end
|
201
221
|
end
|
202
222
|
end
|
@@ -235,10 +255,10 @@ describe Grape::API do
|
|
235
255
|
it 'allows for no path' do
|
236
256
|
subject.namespace :votes do
|
237
257
|
get do
|
238
|
-
|
258
|
+
'Votes'
|
239
259
|
end
|
240
260
|
post do
|
241
|
-
|
261
|
+
'Created a Vote'
|
242
262
|
end
|
243
263
|
end
|
244
264
|
|
@@ -249,21 +269,23 @@ describe Grape::API do
|
|
249
269
|
end
|
250
270
|
|
251
271
|
it 'handles empty calls' do
|
252
|
-
subject.get
|
253
|
-
get
|
254
|
-
expect(last_response.body).to eql
|
272
|
+
subject.get '/'
|
273
|
+
get '/'
|
274
|
+
expect(last_response.body).to eql ''
|
255
275
|
end
|
256
276
|
|
257
277
|
describe 'root routes should work with' do
|
258
278
|
before do
|
259
279
|
subject.format :txt
|
280
|
+
subject.content_type :json, 'application/json'
|
281
|
+
subject.formatter :json, ->(object, env) { object }
|
260
282
|
def subject.enable_root_route!
|
261
|
-
get(
|
283
|
+
get('/') { 'root' }
|
262
284
|
end
|
263
285
|
end
|
264
286
|
|
265
287
|
after do
|
266
|
-
expect(last_response.body).to eql
|
288
|
+
expect(last_response.body).to eql 'root'
|
267
289
|
end
|
268
290
|
|
269
291
|
describe 'path versioned APIs' do
|
@@ -273,11 +295,11 @@ describe Grape::API do
|
|
273
295
|
end
|
274
296
|
|
275
297
|
it 'without a format' do
|
276
|
-
versioned_get
|
298
|
+
versioned_get '/', 'v1', using: :path
|
277
299
|
end
|
278
300
|
|
279
301
|
it 'with a format' do
|
280
|
-
get
|
302
|
+
get '/v1/.json'
|
281
303
|
end
|
282
304
|
end
|
283
305
|
|
@@ -285,41 +307,41 @@ describe Grape::API do
|
|
285
307
|
subject.version 'v1', using: :header, vendor: 'test'
|
286
308
|
subject.enable_root_route!
|
287
309
|
|
288
|
-
versioned_get
|
310
|
+
versioned_get '/', 'v1', using: :header, vendor: 'test'
|
289
311
|
end
|
290
312
|
|
291
313
|
it 'header versioned APIs with multiple headers' do
|
292
|
-
subject.version
|
314
|
+
subject.version %w(v1 v2), using: :header, vendor: 'test'
|
293
315
|
subject.enable_root_route!
|
294
316
|
|
295
|
-
versioned_get
|
296
|
-
versioned_get
|
317
|
+
versioned_get '/', 'v1', using: :header, vendor: 'test'
|
318
|
+
versioned_get '/', 'v2', using: :header, vendor: 'test'
|
297
319
|
end
|
298
320
|
|
299
321
|
it 'param versioned APIs' do
|
300
322
|
subject.version 'v1', using: :param
|
301
323
|
subject.enable_root_route!
|
302
324
|
|
303
|
-
versioned_get
|
325
|
+
versioned_get '/', 'v1', using: :param
|
304
326
|
end
|
305
327
|
|
306
328
|
it 'Accept-Version header versioned APIs' do
|
307
329
|
subject.version 'v1', using: :accept_version_header
|
308
330
|
subject.enable_root_route!
|
309
331
|
|
310
|
-
versioned_get
|
332
|
+
versioned_get '/', 'v1', using: :accept_version_header
|
311
333
|
end
|
312
334
|
|
313
335
|
it 'unversioned APIs' do
|
314
336
|
subject.enable_root_route!
|
315
337
|
|
316
|
-
get
|
338
|
+
get '/'
|
317
339
|
end
|
318
340
|
end
|
319
341
|
|
320
342
|
it 'allows for multiple paths' do
|
321
|
-
subject.get([
|
322
|
-
|
343
|
+
subject.get(['/abc', '/def']) do
|
344
|
+
'foo'
|
323
345
|
end
|
324
346
|
|
325
347
|
get '/abc'
|
@@ -330,10 +352,10 @@ describe Grape::API do
|
|
330
352
|
|
331
353
|
context 'format' do
|
332
354
|
before(:each) do
|
333
|
-
allow_any_instance_of(Object).to receive(:to_json).and_return(
|
334
|
-
allow_any_instance_of(Object).to receive(:to_txt).and_return(
|
355
|
+
allow_any_instance_of(Object).to receive(:to_json).and_return('abc')
|
356
|
+
allow_any_instance_of(Object).to receive(:to_txt).and_return('def')
|
335
357
|
|
336
|
-
subject.get(
|
358
|
+
subject.get('/abc') do
|
337
359
|
Object.new
|
338
360
|
end
|
339
361
|
end
|
@@ -353,7 +375,7 @@ describe Grape::API do
|
|
353
375
|
|
354
376
|
it 'allows for format without corrupting a param' do
|
355
377
|
subject.get('/:id') do
|
356
|
-
{
|
378
|
+
{ 'id' => params[:id] }
|
357
379
|
end
|
358
380
|
|
359
381
|
get '/awesome.json'
|
@@ -363,7 +385,7 @@ describe Grape::API do
|
|
363
385
|
it 'allows for format in namespace with no path' do
|
364
386
|
subject.namespace :abc do
|
365
387
|
get do
|
366
|
-
[
|
388
|
+
['json']
|
367
389
|
end
|
368
390
|
end
|
369
391
|
|
@@ -373,7 +395,7 @@ describe Grape::API do
|
|
373
395
|
|
374
396
|
it 'allows for multiple verbs' do
|
375
397
|
subject.route([:get, :post], '/abc') do
|
376
|
-
|
398
|
+
'hiya'
|
377
399
|
end
|
378
400
|
|
379
401
|
subject.endpoints.first.routes.each do |route|
|
@@ -399,7 +421,7 @@ describe Grape::API do
|
|
399
421
|
expect(last_response.body).to eql MultiJson.dump(object)
|
400
422
|
expect(last_request.params).to eql Hash.new
|
401
423
|
end
|
402
|
-
it
|
424
|
+
it 'stores input in api.request.input' do
|
403
425
|
subject.format :json
|
404
426
|
subject.send(verb) do
|
405
427
|
env['api.request.input']
|
@@ -408,8 +430,8 @@ describe Grape::API do
|
|
408
430
|
expect(last_response.status).to eq(verb == :post ? 201 : 200)
|
409
431
|
expect(last_response.body).to eql MultiJson.dump(object).to_json
|
410
432
|
end
|
411
|
-
context
|
412
|
-
it
|
433
|
+
context 'chunked transfer encoding' do
|
434
|
+
it 'stores input in api.request.input' do
|
413
435
|
subject.format :json
|
414
436
|
subject.send(verb) do
|
415
437
|
env['api.request.input']
|
@@ -424,16 +446,15 @@ describe Grape::API do
|
|
424
446
|
end
|
425
447
|
|
426
448
|
it 'allows for multipart paths' do
|
427
|
-
|
428
449
|
subject.route([:get, :post], '/:id/first') do
|
429
|
-
|
450
|
+
'first'
|
430
451
|
end
|
431
452
|
|
432
453
|
subject.route([:get, :post], '/:id') do
|
433
|
-
|
454
|
+
'ola'
|
434
455
|
end
|
435
456
|
subject.route([:get, :post], '/:id/first/second') do
|
436
|
-
|
457
|
+
'second'
|
437
458
|
end
|
438
459
|
|
439
460
|
get '/1'
|
@@ -446,12 +467,11 @@ describe Grape::API do
|
|
446
467
|
expect(last_response.body).to eql 'first'
|
447
468
|
get '/1/first/second'
|
448
469
|
expect(last_response.body).to eql 'second'
|
449
|
-
|
450
470
|
end
|
451
471
|
|
452
472
|
it 'allows for :any as a verb' do
|
453
473
|
subject.route(:any, '/abc') do
|
454
|
-
|
474
|
+
'lol'
|
455
475
|
end
|
456
476
|
|
457
477
|
%w(get post put delete options patch).each do |m|
|
@@ -476,7 +496,7 @@ describe Grape::API do
|
|
476
496
|
|
477
497
|
it 'returns a 201 response code for POST by default' do
|
478
498
|
subject.post('example') do
|
479
|
-
|
499
|
+
'Created'
|
480
500
|
end
|
481
501
|
|
482
502
|
post '/example'
|
@@ -487,7 +507,7 @@ describe Grape::API do
|
|
487
507
|
it 'returns a 405 for an unsupported method with an X-Custom-Header' do
|
488
508
|
subject.before { header 'X-Custom-Header', 'foo' }
|
489
509
|
subject.get 'example' do
|
490
|
-
|
510
|
+
'example'
|
491
511
|
end
|
492
512
|
put '/example'
|
493
513
|
expect(last_response.status).to eql 405
|
@@ -497,10 +517,10 @@ describe Grape::API do
|
|
497
517
|
|
498
518
|
specify '405 responses includes an Allow header specifying supported methods' do
|
499
519
|
subject.get 'example' do
|
500
|
-
|
520
|
+
'example'
|
501
521
|
end
|
502
522
|
subject.post 'example' do
|
503
|
-
|
523
|
+
'example'
|
504
524
|
end
|
505
525
|
put '/example'
|
506
526
|
expect(last_response.headers['Allow']).to eql 'OPTIONS, GET, POST, HEAD'
|
@@ -508,10 +528,10 @@ describe Grape::API do
|
|
508
528
|
|
509
529
|
specify '405 responses includes an Content-Type header' do
|
510
530
|
subject.get 'example' do
|
511
|
-
|
531
|
+
'example'
|
512
532
|
end
|
513
533
|
subject.post 'example' do
|
514
|
-
|
534
|
+
'example'
|
515
535
|
end
|
516
536
|
put '/example'
|
517
537
|
expect(last_response.headers['Content-Type']).to eql 'text/plain'
|
@@ -520,7 +540,7 @@ describe Grape::API do
|
|
520
540
|
it 'adds an OPTIONS route that returns a 204, an Allow header and a X-Custom-Header' do
|
521
541
|
subject.before { header 'X-Custom-Header', 'foo' }
|
522
542
|
subject.get 'example' do
|
523
|
-
|
543
|
+
'example'
|
524
544
|
end
|
525
545
|
options '/example'
|
526
546
|
expect(last_response.status).to eql 204
|
@@ -531,7 +551,7 @@ describe Grape::API do
|
|
531
551
|
|
532
552
|
it 'allows HEAD on a GET request' do
|
533
553
|
subject.get 'example' do
|
534
|
-
|
554
|
+
'example'
|
535
555
|
end
|
536
556
|
head '/example'
|
537
557
|
expect(last_response.status).to eql 200
|
@@ -543,18 +563,18 @@ describe Grape::API do
|
|
543
563
|
error! 'nothing to see here', 400
|
544
564
|
end
|
545
565
|
subject.get 'example' do
|
546
|
-
|
566
|
+
'example'
|
547
567
|
end
|
548
568
|
head '/example'
|
549
569
|
expect(last_response.status).to eql 400
|
550
570
|
end
|
551
571
|
end
|
552
572
|
|
553
|
-
context
|
573
|
+
context 'do_not_route_head!' do
|
554
574
|
before :each do
|
555
575
|
subject.do_not_route_head!
|
556
576
|
subject.get 'example' do
|
557
|
-
|
577
|
+
'example'
|
558
578
|
end
|
559
579
|
end
|
560
580
|
it 'options does not contain HEAD' do
|
@@ -569,11 +589,11 @@ describe Grape::API do
|
|
569
589
|
end
|
570
590
|
end
|
571
591
|
|
572
|
-
context
|
592
|
+
context 'do_not_route_options!' do
|
573
593
|
before :each do
|
574
594
|
subject.do_not_route_options!
|
575
595
|
subject.get 'example' do
|
576
|
-
|
596
|
+
'example'
|
577
597
|
end
|
578
598
|
end
|
579
599
|
it 'options does not exist' do
|
@@ -629,7 +649,7 @@ describe Grape::API do
|
|
629
649
|
"#{@foo} #{@bar}"
|
630
650
|
end
|
631
651
|
|
632
|
-
get '/', id:
|
652
|
+
get '/', id: '32'
|
633
653
|
expect(last_response.body).to eql 'first 32:Fixnum second'
|
634
654
|
end
|
635
655
|
|
@@ -737,17 +757,42 @@ describe Grape::API do
|
|
737
757
|
|
738
758
|
context 'format' do
|
739
759
|
before do
|
740
|
-
subject.get(
|
760
|
+
subject.get('/foo') { 'bar' }
|
741
761
|
end
|
742
762
|
|
743
763
|
it 'sets content type for txt format' do
|
744
764
|
get '/foo'
|
745
|
-
expect(last_response.headers['Content-Type']).to
|
765
|
+
expect(last_response.headers['Content-Type']).to eq('text/plain')
|
766
|
+
end
|
767
|
+
|
768
|
+
it 'sets content type for xml' do
|
769
|
+
get '/foo.xml'
|
770
|
+
expect(last_response.headers['Content-Type']).to eq('application/xml')
|
746
771
|
end
|
747
772
|
|
748
773
|
it 'sets content type for json' do
|
749
774
|
get '/foo.json'
|
750
|
-
expect(last_response.headers['Content-Type']).to
|
775
|
+
expect(last_response.headers['Content-Type']).to eq('application/json')
|
776
|
+
end
|
777
|
+
|
778
|
+
it 'sets content type for serializable hash format' do
|
779
|
+
get '/foo.serializable_hash'
|
780
|
+
expect(last_response.headers['Content-Type']).to eq('application/json')
|
781
|
+
end
|
782
|
+
|
783
|
+
it 'sets content type for binary format' do
|
784
|
+
get '/foo.binary'
|
785
|
+
expect(last_response.headers['Content-Type']).to eq('application/octet-stream')
|
786
|
+
end
|
787
|
+
|
788
|
+
it 'returns raw data when content type binary' do
|
789
|
+
image_filename = 'grape.png'
|
790
|
+
file = File.open(image_filename, 'rb') { |io| io.read }
|
791
|
+
subject.format :binary
|
792
|
+
subject.get('/binary_file') { File.binread(image_filename) }
|
793
|
+
get '/binary_file'
|
794
|
+
expect(last_response.headers['Content-Type']).to eq('application/octet-stream')
|
795
|
+
expect(last_response.body).to eq(file)
|
751
796
|
end
|
752
797
|
|
753
798
|
it 'sets content type for error' do
|
@@ -756,24 +801,24 @@ describe Grape::API do
|
|
756
801
|
expect(last_response.headers['Content-Type']).to eql 'text/plain'
|
757
802
|
end
|
758
803
|
|
759
|
-
it 'sets content type for error' do
|
804
|
+
it 'sets content type for json error' do
|
760
805
|
subject.format :json
|
761
806
|
subject.get('/error') { error!('error in json', 500) }
|
762
|
-
get '/error
|
807
|
+
get '/error'
|
763
808
|
expect(last_response.headers['Content-Type']).to eql 'application/json'
|
764
809
|
end
|
765
810
|
|
766
|
-
it 'sets content type for xml' do
|
811
|
+
it 'sets content type for xml error' do
|
767
812
|
subject.format :xml
|
768
813
|
subject.get('/error') { error!('error in xml', 500) }
|
769
|
-
get '/error
|
814
|
+
get '/error'
|
770
815
|
expect(last_response.headers['Content-Type']).to eql 'application/xml'
|
771
816
|
end
|
772
817
|
|
773
818
|
context 'with a custom content_type' do
|
774
819
|
before do
|
775
820
|
subject.content_type :custom, 'application/custom'
|
776
|
-
subject.formatter :custom,
|
821
|
+
subject.formatter :custom, ->(object, env) { 'custom' }
|
777
822
|
|
778
823
|
subject.get('/custom') { 'bar' }
|
779
824
|
subject.get('/error') { error!('error in custom', 500) }
|
@@ -792,21 +837,21 @@ describe Grape::API do
|
|
792
837
|
|
793
838
|
context 'env["api.format"]' do
|
794
839
|
before do
|
795
|
-
subject.post
|
840
|
+
subject.post 'attachment' do
|
796
841
|
filename = params[:file][:filename]
|
797
842
|
content_type MIME::Types.type_for(filename)[0].to_s
|
798
843
|
env['api.format'] = :binary # there's no formatter for :binary, data will be returned "as is"
|
799
|
-
header
|
844
|
+
header 'Content-Disposition', "attachment; filename*=UTF-8''#{URI.escape(filename)}"
|
800
845
|
params[:file][:tempfile].read
|
801
846
|
end
|
802
847
|
end
|
803
848
|
|
804
849
|
['/attachment.png', 'attachment'].each do |url|
|
805
850
|
it "uploads and downloads a PNG file via #{url}" do
|
806
|
-
image_filename =
|
851
|
+
image_filename = 'grape.png'
|
807
852
|
post url, file: Rack::Test::UploadedFile.new(image_filename, 'image/png', true)
|
808
853
|
expect(last_response.status).to eq(201)
|
809
|
-
expect(last_response.headers['Content-Type']).to eq(
|
854
|
+
expect(last_response.headers['Content-Type']).to eq('image/png')
|
810
855
|
expect(last_response.headers['Content-Disposition']).to eq("attachment; filename*=UTF-8''grape.png")
|
811
856
|
File.open(image_filename, 'rb') do |io|
|
812
857
|
expect(last_response.body).to eq io.read
|
@@ -814,11 +859,11 @@ describe Grape::API do
|
|
814
859
|
end
|
815
860
|
end
|
816
861
|
|
817
|
-
it
|
862
|
+
it 'uploads and downloads a Ruby file' do
|
818
863
|
filename = __FILE__
|
819
|
-
post
|
864
|
+
post '/attachment.rb', file: Rack::Test::UploadedFile.new(filename, 'application/x-ruby', true)
|
820
865
|
expect(last_response.status).to eq(201)
|
821
|
-
expect(last_response.headers['Content-Type']).to eq(
|
866
|
+
expect(last_response.headers['Content-Type']).to eq('application/x-ruby')
|
822
867
|
expect(last_response.headers['Content-Disposition']).to eq("attachment; filename*=UTF-8''api_spec.rb")
|
823
868
|
File.open(filename, 'rb') do |io|
|
824
869
|
expect(last_response.body).to eq io.read
|
@@ -847,20 +892,14 @@ describe Grape::API do
|
|
847
892
|
|
848
893
|
describe '.middleware' do
|
849
894
|
it 'includes middleware arguments from settings' do
|
850
|
-
|
851
|
-
allow(settings).to receive(:stack).and_return([{ middleware: [[ApiSpec::PhonyMiddleware, 'abc', 123]] }])
|
852
|
-
allow(subject).to receive(:settings).and_return(settings)
|
895
|
+
subject.use ApiSpec::PhonyMiddleware, 'abc', 123
|
853
896
|
expect(subject.middleware).to eql [[ApiSpec::PhonyMiddleware, 'abc', 123]]
|
854
897
|
end
|
855
898
|
|
856
899
|
it 'includes all middleware from stacked settings' do
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
{ middleware: [[ApiSpec::PhonyMiddleware, 'foo']] }
|
861
|
-
]
|
862
|
-
|
863
|
-
allow(subject).to receive(:settings).and_return(settings)
|
900
|
+
subject.use ApiSpec::PhonyMiddleware, 123
|
901
|
+
subject.use ApiSpec::PhonyMiddleware, 'abc'
|
902
|
+
subject.use ApiSpec::PhonyMiddleware, 'foo'
|
864
903
|
|
865
904
|
expect(subject.middleware).to eql [
|
866
905
|
[ApiSpec::PhonyMiddleware, 123],
|
@@ -899,13 +938,13 @@ describe Grape::API do
|
|
899
938
|
end
|
900
939
|
|
901
940
|
it 'adds a block if one is given' do
|
902
|
-
block =
|
941
|
+
block = -> {}
|
903
942
|
subject.use ApiSpec::PhonyMiddleware, &block
|
904
943
|
expect(subject.middleware).to eql [[ApiSpec::PhonyMiddleware, block]]
|
905
944
|
end
|
906
945
|
|
907
946
|
it 'uses a block if one is given' do
|
908
|
-
block =
|
947
|
+
block = -> {}
|
909
948
|
subject.use ApiSpec::PhonyMiddleware, &block
|
910
949
|
subject.get '/' do
|
911
950
|
env['phony.block'].inspect
|
@@ -916,7 +955,7 @@ describe Grape::API do
|
|
916
955
|
end
|
917
956
|
|
918
957
|
it 'does not destroy the middleware settings on multiple runs' do
|
919
|
-
block =
|
958
|
+
block = -> {}
|
920
959
|
subject.use ApiSpec::PhonyMiddleware, &block
|
921
960
|
subject.get '/' do
|
922
961
|
env['phony.block'].inspect
|
@@ -931,15 +970,15 @@ describe Grape::API do
|
|
931
970
|
it 'mounts behind error middleware' do
|
932
971
|
m = Class.new(Grape::Middleware::Base) do
|
933
972
|
def before
|
934
|
-
throw :error, message:
|
973
|
+
throw :error, message: 'Caught in the Net', status: 400
|
935
974
|
end
|
936
975
|
end
|
937
976
|
subject.use m
|
938
|
-
subject.get
|
977
|
+
subject.get '/' do
|
939
978
|
end
|
940
|
-
get
|
979
|
+
get '/'
|
941
980
|
expect(last_response.status).to eq(400)
|
942
|
-
expect(last_response.body).to eq(
|
981
|
+
expect(last_response.body).to eq('Caught in the Net')
|
943
982
|
end
|
944
983
|
end
|
945
984
|
end
|
@@ -948,7 +987,7 @@ describe Grape::API do
|
|
948
987
|
subject.http_basic do |u, p|
|
949
988
|
u == 'allow'
|
950
989
|
end
|
951
|
-
subject.get(:hello) {
|
990
|
+
subject.get(:hello) { 'Hello, world.' }
|
952
991
|
get '/hello'
|
953
992
|
expect(last_response.status).to eql 401
|
954
993
|
get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('allow', 'whatever')
|
@@ -956,13 +995,13 @@ describe Grape::API do
|
|
956
995
|
end
|
957
996
|
|
958
997
|
it 'is scopable' do
|
959
|
-
subject.get(:hello) {
|
998
|
+
subject.get(:hello) { 'Hello, world.' }
|
960
999
|
subject.namespace :admin do
|
961
1000
|
http_basic do |u, p|
|
962
1001
|
u == 'allow'
|
963
1002
|
end
|
964
1003
|
|
965
|
-
get(:hello) {
|
1004
|
+
get(:hello) { 'Hello, world.' }
|
966
1005
|
end
|
967
1006
|
|
968
1007
|
get '/hello'
|
@@ -976,7 +1015,7 @@ describe Grape::API do
|
|
976
1015
|
u == 'allow'
|
977
1016
|
end
|
978
1017
|
|
979
|
-
subject.get(:hello) {
|
1018
|
+
subject.get(:hello) { 'Hello, world.' }
|
980
1019
|
get '/hello'
|
981
1020
|
expect(last_response.status).to eql 401
|
982
1021
|
get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('allow', 'whatever')
|
@@ -992,7 +1031,7 @@ describe Grape::API do
|
|
992
1031
|
u == 'allow'
|
993
1032
|
end
|
994
1033
|
|
995
|
-
subject.get(:hello) {
|
1034
|
+
subject.get(:hello) { 'Hello, world.' }
|
996
1035
|
get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('allow', 'whatever')
|
997
1036
|
expect(basic_auth_context).to be_an_instance_of(Grape::Endpoint)
|
998
1037
|
end
|
@@ -1008,7 +1047,7 @@ describe Grape::API do
|
|
1008
1047
|
authorize(u, p)
|
1009
1048
|
end
|
1010
1049
|
|
1011
|
-
subject.get(:hello) {
|
1050
|
+
subject.get(:hello) { 'Hello, world.' }
|
1012
1051
|
get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('allow', 'whatever')
|
1013
1052
|
expect(last_response.status).to eql 200
|
1014
1053
|
get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('disallow', 'whatever')
|
@@ -1017,7 +1056,7 @@ describe Grape::API do
|
|
1017
1056
|
|
1018
1057
|
it 'can set instance variables accessible to routes' do
|
1019
1058
|
subject.http_basic do |u, p|
|
1020
|
-
@hello =
|
1059
|
+
@hello = 'Hello, world.'
|
1021
1060
|
|
1022
1061
|
u == 'allow'
|
1023
1062
|
end
|
@@ -1025,11 +1064,20 @@ describe Grape::API do
|
|
1025
1064
|
subject.get(:hello) { @hello }
|
1026
1065
|
get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('allow', 'whatever')
|
1027
1066
|
expect(last_response.status).to eql 200
|
1028
|
-
expect(last_response.body).to eql
|
1067
|
+
expect(last_response.body).to eql 'Hello, world.'
|
1029
1068
|
end
|
1030
1069
|
end
|
1031
1070
|
|
1032
1071
|
describe '.logger' do
|
1072
|
+
subject do
|
1073
|
+
Class.new(Grape::API) do
|
1074
|
+
def self.io
|
1075
|
+
@io ||= StringIO.new
|
1076
|
+
end
|
1077
|
+
logger ::Logger.new(io)
|
1078
|
+
end
|
1079
|
+
end
|
1080
|
+
|
1033
1081
|
it 'returns an instance of Logger class by default' do
|
1034
1082
|
expect(subject.logger.class).to eql Logger
|
1035
1083
|
end
|
@@ -1038,14 +1086,18 @@ describe Grape::API do
|
|
1038
1086
|
mylogger = Class.new
|
1039
1087
|
subject.logger mylogger
|
1040
1088
|
expect(mylogger).to receive(:info).exactly(1).times
|
1041
|
-
subject.logger.info
|
1089
|
+
subject.logger.info 'this will be logged'
|
1042
1090
|
end
|
1043
1091
|
|
1044
|
-
it
|
1092
|
+
it 'defaults to a standard logger log format' do
|
1045
1093
|
t = Time.at(100)
|
1046
1094
|
allow(Time).to receive(:now).and_return(t)
|
1047
|
-
|
1048
|
-
|
1095
|
+
if ActiveSupport::VERSION::MAJOR >= 4
|
1096
|
+
expect(subject.io).to receive(:write).with("I, [#{Logger::Formatter.new.send(:format_datetime, t)}\##{Process.pid}] INFO -- : this will be logged\n")
|
1097
|
+
else
|
1098
|
+
expect(subject.io).to receive(:write).with("this will be logged\n")
|
1099
|
+
end
|
1100
|
+
subject.logger.info 'this will be logged'
|
1049
1101
|
end
|
1050
1102
|
end
|
1051
1103
|
|
@@ -1053,7 +1105,7 @@ describe Grape::API do
|
|
1053
1105
|
it 'is accessible from the endpoint' do
|
1054
1106
|
subject.helpers do
|
1055
1107
|
def hello
|
1056
|
-
|
1108
|
+
'Hello, world.'
|
1057
1109
|
end
|
1058
1110
|
end
|
1059
1111
|
|
@@ -1117,7 +1169,7 @@ describe Grape::API do
|
|
1117
1169
|
it 'allows for modules' do
|
1118
1170
|
mod = Module.new do
|
1119
1171
|
def hello
|
1120
|
-
|
1172
|
+
'Hello, world.'
|
1121
1173
|
end
|
1122
1174
|
end
|
1123
1175
|
subject.helpers mod
|
@@ -1184,7 +1236,7 @@ describe Grape::API do
|
|
1184
1236
|
describe '.rescue_from' do
|
1185
1237
|
it 'does not rescue errors when rescue_from is not set' do
|
1186
1238
|
subject.get '/exception' do
|
1187
|
-
|
1239
|
+
fail 'rain!'
|
1188
1240
|
end
|
1189
1241
|
expect { get '/exception' }.to raise_error
|
1190
1242
|
end
|
@@ -1192,16 +1244,29 @@ describe Grape::API do
|
|
1192
1244
|
it 'rescues all errors if rescue_from :all is called' do
|
1193
1245
|
subject.rescue_from :all
|
1194
1246
|
subject.get '/exception' do
|
1195
|
-
|
1247
|
+
fail 'rain!'
|
1248
|
+
end
|
1249
|
+
get '/exception'
|
1250
|
+
expect(last_response.status).to eql 500
|
1251
|
+
expect(last_response.body).to eq 'rain!'
|
1252
|
+
end
|
1253
|
+
|
1254
|
+
it 'rescues all errors with a json formatter' do
|
1255
|
+
subject.format :json
|
1256
|
+
subject.default_format :json
|
1257
|
+
subject.rescue_from :all
|
1258
|
+
subject.get '/exception' do
|
1259
|
+
fail 'rain!'
|
1196
1260
|
end
|
1197
1261
|
get '/exception'
|
1198
1262
|
expect(last_response.status).to eql 500
|
1263
|
+
expect(last_response.body).to eq({ error: 'rain!' }.to_json)
|
1199
1264
|
end
|
1200
1265
|
|
1201
1266
|
it 'rescues only certain errors if rescue_from is called with specific errors' do
|
1202
1267
|
subject.rescue_from ArgumentError
|
1203
|
-
subject.get('/rescued') {
|
1204
|
-
subject.get('/unrescued') {
|
1268
|
+
subject.get('/rescued') { fail ArgumentError }
|
1269
|
+
subject.get('/unrescued') { fail 'beefcake' }
|
1205
1270
|
|
1206
1271
|
get '/rescued'
|
1207
1272
|
expect(last_response.status).to eql 500
|
@@ -1215,7 +1280,7 @@ describe Grape::API do
|
|
1215
1280
|
end
|
1216
1281
|
|
1217
1282
|
it 'does not re-raise exceptions of type Grape::Exceptions::Base' do
|
1218
|
-
subject.get('/custom_exception') {
|
1283
|
+
subject.get('/custom_exception') { fail CustomError }
|
1219
1284
|
|
1220
1285
|
expect { get '/custom_exception' }.not_to raise_error
|
1221
1286
|
end
|
@@ -1225,7 +1290,7 @@ describe Grape::API do
|
|
1225
1290
|
rack_response('New Error', e.status)
|
1226
1291
|
end
|
1227
1292
|
subject.get '/custom_error' do
|
1228
|
-
|
1293
|
+
fail CustomError, status: 400, message: 'Custom Error'
|
1229
1294
|
end
|
1230
1295
|
|
1231
1296
|
get '/custom_error'
|
@@ -1236,7 +1301,7 @@ describe Grape::API do
|
|
1236
1301
|
|
1237
1302
|
it 'can rescue exceptions raised in the formatter' do
|
1238
1303
|
formatter = double(:formatter)
|
1239
|
-
allow(formatter).to receive(:call) {
|
1304
|
+
allow(formatter).to receive(:call) { fail StandardError }
|
1240
1305
|
allow(Grape::Formatter::Base).to receive(:formatter_for) { formatter }
|
1241
1306
|
|
1242
1307
|
subject.rescue_from :all do |e|
|
@@ -1256,7 +1321,7 @@ describe Grape::API do
|
|
1256
1321
|
rack_response("rescued from #{e.message}", 202)
|
1257
1322
|
end
|
1258
1323
|
subject.get '/exception' do
|
1259
|
-
|
1324
|
+
fail 'rain!'
|
1260
1325
|
end
|
1261
1326
|
get '/exception'
|
1262
1327
|
expect(last_response.status).to eql 202
|
@@ -1275,7 +1340,7 @@ describe Grape::API do
|
|
1275
1340
|
rack_response("rescued from #{e.class.name}", 500)
|
1276
1341
|
end
|
1277
1342
|
subject.get '/exception' do
|
1278
|
-
|
1343
|
+
fail ConnectionError
|
1279
1344
|
end
|
1280
1345
|
get '/exception'
|
1281
1346
|
expect(last_response.status).to eql 500
|
@@ -1286,7 +1351,7 @@ describe Grape::API do
|
|
1286
1351
|
rack_response("rescued from #{e.class.name}", 500)
|
1287
1352
|
end
|
1288
1353
|
subject.get '/exception' do
|
1289
|
-
|
1354
|
+
fail ConnectionError
|
1290
1355
|
end
|
1291
1356
|
get '/exception'
|
1292
1357
|
expect(last_response.status).to eql 500
|
@@ -1297,7 +1362,7 @@ describe Grape::API do
|
|
1297
1362
|
rack_response("rescued from #{e.class.name}", 500)
|
1298
1363
|
end
|
1299
1364
|
subject.get '/exception' do
|
1300
|
-
|
1365
|
+
fail ConnectionError
|
1301
1366
|
end
|
1302
1367
|
get '/exception'
|
1303
1368
|
expect(last_response.status).to eql 500
|
@@ -1311,10 +1376,10 @@ describe Grape::API do
|
|
1311
1376
|
rack_response("rescued from #{e.class.name}", 500)
|
1312
1377
|
end
|
1313
1378
|
subject.get '/connection' do
|
1314
|
-
|
1379
|
+
fail ConnectionError
|
1315
1380
|
end
|
1316
1381
|
subject.get '/database' do
|
1317
|
-
|
1382
|
+
fail DatabaseError
|
1318
1383
|
end
|
1319
1384
|
get '/connection'
|
1320
1385
|
expect(last_response.status).to eql 500
|
@@ -1328,7 +1393,7 @@ describe Grape::API do
|
|
1328
1393
|
rack_response("rescued from #{e.class.name}", 500)
|
1329
1394
|
end
|
1330
1395
|
subject.get '/uncaught' do
|
1331
|
-
|
1396
|
+
fail CommunicationError
|
1332
1397
|
end
|
1333
1398
|
expect { get '/uncaught' }.to raise_error(CommunicationError)
|
1334
1399
|
end
|
@@ -1337,21 +1402,21 @@ describe Grape::API do
|
|
1337
1402
|
|
1338
1403
|
describe '.rescue_from klass, lambda' do
|
1339
1404
|
it 'rescues an error with the lambda' do
|
1340
|
-
subject.rescue_from ArgumentError,
|
1341
|
-
rack_response(
|
1405
|
+
subject.rescue_from ArgumentError, -> {
|
1406
|
+
rack_response('rescued with a lambda', 400)
|
1342
1407
|
}
|
1343
|
-
subject.get('/rescue_lambda') {
|
1408
|
+
subject.get('/rescue_lambda') { fail ArgumentError }
|
1344
1409
|
|
1345
1410
|
get '/rescue_lambda'
|
1346
1411
|
expect(last_response.status).to eq(400)
|
1347
|
-
expect(last_response.body).to eq(
|
1412
|
+
expect(last_response.body).to eq('rescued with a lambda')
|
1348
1413
|
end
|
1349
1414
|
|
1350
1415
|
it 'can execute the lambda with an argument' do
|
1351
|
-
subject.rescue_from ArgumentError,
|
1416
|
+
subject.rescue_from ArgumentError, ->(e) {
|
1352
1417
|
rack_response(e.message, 400)
|
1353
1418
|
}
|
1354
|
-
subject.get('/rescue_lambda') {
|
1419
|
+
subject.get('/rescue_lambda') { fail ArgumentError, 'lambda takes an argument' }
|
1355
1420
|
|
1356
1421
|
get '/rescue_lambda'
|
1357
1422
|
expect(last_response.status).to eq(400)
|
@@ -1366,7 +1431,7 @@ describe Grape::API do
|
|
1366
1431
|
end
|
1367
1432
|
|
1368
1433
|
subject.rescue_from ArgumentError, with: rescue_arg_error
|
1369
|
-
subject.get('/rescue_method') {
|
1434
|
+
subject.get('/rescue_method') { fail ArgumentError }
|
1370
1435
|
|
1371
1436
|
get '/rescue_method'
|
1372
1437
|
expect(last_response.status).to eq(400)
|
@@ -1387,13 +1452,13 @@ describe Grape::API do
|
|
1387
1452
|
rack_response("rescued from #{e.class.name}", 500)
|
1388
1453
|
end
|
1389
1454
|
subject.get '/caught_child' do
|
1390
|
-
|
1455
|
+
fail APIErrors::ChildError
|
1391
1456
|
end
|
1392
1457
|
subject.get '/caught_parent' do
|
1393
|
-
|
1458
|
+
fail APIErrors::ParentError
|
1394
1459
|
end
|
1395
1460
|
subject.get '/uncaught_parent' do
|
1396
|
-
|
1461
|
+
fail StandardError
|
1397
1462
|
end
|
1398
1463
|
|
1399
1464
|
get '/caught_child'
|
@@ -1408,7 +1473,7 @@ describe Grape::API do
|
|
1408
1473
|
rack_response("rescued from #{e.class.name}", 500)
|
1409
1474
|
end
|
1410
1475
|
subject.get '/caught_child' do
|
1411
|
-
|
1476
|
+
fail APIErrors::ChildError
|
1412
1477
|
end
|
1413
1478
|
|
1414
1479
|
get '/caught_child'
|
@@ -1420,7 +1485,7 @@ describe Grape::API do
|
|
1420
1485
|
rack_response("rescued from #{e.class.name}", 500)
|
1421
1486
|
end
|
1422
1487
|
subject.get '/uncaught' do
|
1423
|
-
|
1488
|
+
fail APIErrors::ChildError
|
1424
1489
|
end
|
1425
1490
|
expect { get '/uncaught' }.to raise_error(APIErrors::ChildError)
|
1426
1491
|
end
|
@@ -1431,17 +1496,17 @@ describe Grape::API do
|
|
1431
1496
|
subject.rescue_from :all
|
1432
1497
|
subject.format :txt
|
1433
1498
|
subject.get '/exception' do
|
1434
|
-
|
1499
|
+
fail 'rain!'
|
1435
1500
|
end
|
1436
1501
|
get '/exception'
|
1437
|
-
expect(last_response.body).to eql
|
1502
|
+
expect(last_response.body).to eql 'rain!'
|
1438
1503
|
end
|
1439
1504
|
|
1440
1505
|
it 'rescues all errors and return :txt with backtrace' do
|
1441
1506
|
subject.rescue_from :all, backtrace: true
|
1442
1507
|
subject.format :txt
|
1443
1508
|
subject.get '/exception' do
|
1444
|
-
|
1509
|
+
fail 'rain!'
|
1445
1510
|
end
|
1446
1511
|
get '/exception'
|
1447
1512
|
expect(last_response.body.start_with?("rain!\r\n")).to be true
|
@@ -1449,22 +1514,22 @@ describe Grape::API do
|
|
1449
1514
|
|
1450
1515
|
it 'rescues all errors with a default formatter' do
|
1451
1516
|
subject.default_format :foo
|
1452
|
-
subject.content_type :foo,
|
1517
|
+
subject.content_type :foo, 'text/foo'
|
1453
1518
|
subject.rescue_from :all
|
1454
1519
|
subject.get '/exception' do
|
1455
|
-
|
1520
|
+
fail 'rain!'
|
1456
1521
|
end
|
1457
1522
|
get '/exception.foo'
|
1458
|
-
expect(last_response.body).to start_with
|
1523
|
+
expect(last_response.body).to start_with 'rain!'
|
1459
1524
|
end
|
1460
1525
|
|
1461
1526
|
it 'defaults the error formatter to format' do
|
1462
1527
|
subject.format :json
|
1463
1528
|
subject.rescue_from :all
|
1464
|
-
subject.content_type :json,
|
1465
|
-
subject.content_type :foo,
|
1529
|
+
subject.content_type :json, 'application/json'
|
1530
|
+
subject.content_type :foo, 'text/foo'
|
1466
1531
|
subject.get '/exception' do
|
1467
|
-
|
1532
|
+
fail 'rain!'
|
1468
1533
|
end
|
1469
1534
|
get '/exception.json'
|
1470
1535
|
expect(last_response.body).to eq('{"error":"rain!"}')
|
@@ -1484,10 +1549,10 @@ describe Grape::API do
|
|
1484
1549
|
subject.rescue_from :all, backtrace: true
|
1485
1550
|
subject.error_formatter :txt, CustomErrorFormatter
|
1486
1551
|
subject.get '/exception' do
|
1487
|
-
|
1552
|
+
fail 'rain!'
|
1488
1553
|
end
|
1489
1554
|
get '/exception'
|
1490
|
-
expect(last_response.body).to eq(
|
1555
|
+
expect(last_response.body).to eq('message: rain! @backtrace')
|
1491
1556
|
end
|
1492
1557
|
end
|
1493
1558
|
|
@@ -1504,7 +1569,7 @@ describe Grape::API do
|
|
1504
1569
|
it 'returns a custom error format' do
|
1505
1570
|
subject.rescue_from :all, backtrace: true
|
1506
1571
|
subject.error_formatter :txt, with: CustomErrorFormatter
|
1507
|
-
subject.get('/exception') {
|
1572
|
+
subject.get('/exception') { fail 'rain!' }
|
1508
1573
|
|
1509
1574
|
get '/exception'
|
1510
1575
|
expect(last_response.body).to eq('message: rain! @backtrace')
|
@@ -1516,7 +1581,7 @@ describe Grape::API do
|
|
1516
1581
|
subject.rescue_from :all
|
1517
1582
|
subject.format :json
|
1518
1583
|
subject.get '/exception' do
|
1519
|
-
|
1584
|
+
fail 'rain!'
|
1520
1585
|
end
|
1521
1586
|
get '/exception'
|
1522
1587
|
expect(last_response.body).to eql '{"error":"rain!"}'
|
@@ -1525,25 +1590,25 @@ describe Grape::API do
|
|
1525
1590
|
subject.rescue_from :all, backtrace: true
|
1526
1591
|
subject.format :json
|
1527
1592
|
subject.get '/exception' do
|
1528
|
-
|
1593
|
+
fail 'rain!'
|
1529
1594
|
end
|
1530
1595
|
get '/exception'
|
1531
1596
|
json = MultiJson.load(last_response.body)
|
1532
|
-
expect(json[
|
1533
|
-
expect(json[
|
1597
|
+
expect(json['error']).to eql 'rain!'
|
1598
|
+
expect(json['backtrace'].length).to be > 0
|
1534
1599
|
end
|
1535
1600
|
it 'rescues error! and return txt' do
|
1536
1601
|
subject.format :txt
|
1537
1602
|
subject.get '/error' do
|
1538
|
-
error!(
|
1603
|
+
error!('Access Denied', 401)
|
1539
1604
|
end
|
1540
1605
|
get '/error'
|
1541
|
-
expect(last_response.body).to eql
|
1606
|
+
expect(last_response.body).to eql 'Access Denied'
|
1542
1607
|
end
|
1543
1608
|
it 'rescues error! and return json' do
|
1544
1609
|
subject.format :json
|
1545
1610
|
subject.get '/error' do
|
1546
|
-
error!(
|
1611
|
+
error!('Access Denied', 401)
|
1547
1612
|
end
|
1548
1613
|
get '/error'
|
1549
1614
|
expect(last_response.body).to eql '{"error":"Access Denied"}'
|
@@ -1552,25 +1617,25 @@ describe Grape::API do
|
|
1552
1617
|
|
1553
1618
|
describe '.content_type' do
|
1554
1619
|
it 'sets additional content-type' do
|
1555
|
-
subject.content_type :xls,
|
1620
|
+
subject.content_type :xls, 'application/vnd.ms-excel'
|
1556
1621
|
subject.get :excel do
|
1557
|
-
|
1622
|
+
'some binary content'
|
1558
1623
|
end
|
1559
1624
|
get '/excel.xls'
|
1560
|
-
expect(last_response.content_type).to eq(
|
1625
|
+
expect(last_response.content_type).to eq('application/vnd.ms-excel')
|
1561
1626
|
end
|
1562
1627
|
it 'allows to override content-type' do
|
1563
1628
|
subject.get :content do
|
1564
|
-
content_type
|
1565
|
-
|
1629
|
+
content_type 'text/javascript'
|
1630
|
+
'var x = 1;'
|
1566
1631
|
end
|
1567
1632
|
get '/content'
|
1568
|
-
expect(last_response.content_type).to eq(
|
1633
|
+
expect(last_response.content_type).to eq('text/javascript')
|
1569
1634
|
end
|
1570
1635
|
it 'removes existing content types' do
|
1571
|
-
subject.content_type :xls,
|
1636
|
+
subject.content_type :xls, 'application/vnd.ms-excel'
|
1572
1637
|
subject.get :excel do
|
1573
|
-
|
1638
|
+
'some binary content'
|
1574
1639
|
end
|
1575
1640
|
get '/excel.json'
|
1576
1641
|
expect(last_response.status).to eq(406)
|
@@ -1581,8 +1646,8 @@ describe Grape::API do
|
|
1581
1646
|
describe '.formatter' do
|
1582
1647
|
context 'multiple formatters' do
|
1583
1648
|
before :each do
|
1584
|
-
subject.formatter :json,
|
1585
|
-
subject.formatter :txt,
|
1649
|
+
subject.formatter :json, ->(object, env) { "{\"custom_formatter\":\"#{object[:some] }\"}" }
|
1650
|
+
subject.formatter :txt, ->(object, env) { "custom_formatter: #{object[:some] }" }
|
1586
1651
|
subject.get :simple do
|
1587
1652
|
{ some: 'hash' }
|
1588
1653
|
end
|
@@ -1600,7 +1665,7 @@ describe Grape::API do
|
|
1600
1665
|
before :each do
|
1601
1666
|
subject.content_type :json, 'application/json'
|
1602
1667
|
subject.content_type :custom, 'application/custom'
|
1603
|
-
subject.formatter :custom,
|
1668
|
+
subject.formatter :custom, ->(object, env) { "{\"custom_formatter\":\"#{object[:some] }\"}" }
|
1604
1669
|
subject.get :simple do
|
1605
1670
|
{ some: 'hash' }
|
1606
1671
|
end
|
@@ -1645,24 +1710,24 @@ describe Grape::API do
|
|
1645
1710
|
subject.post '/data' do
|
1646
1711
|
{ x: params[:x] }
|
1647
1712
|
end
|
1648
|
-
post
|
1713
|
+
post '/data', '{"x":42}', 'CONTENT_TYPE' => 'application/json'
|
1649
1714
|
expect(last_response.status).to eq(201)
|
1650
1715
|
expect(last_response.body).to eq('{"x":42}')
|
1651
1716
|
end
|
1652
1717
|
context 'lambda parser' do
|
1653
1718
|
before :each do
|
1654
|
-
subject.content_type :txt,
|
1655
|
-
subject.content_type :custom,
|
1656
|
-
subject.parser :custom,
|
1719
|
+
subject.content_type :txt, 'text/plain'
|
1720
|
+
subject.content_type :custom, 'text/custom'
|
1721
|
+
subject.parser :custom, ->(object, env) { { object.to_sym => object.to_s.reverse } }
|
1657
1722
|
subject.put :simple do
|
1658
1723
|
params[:simple]
|
1659
1724
|
end
|
1660
1725
|
end
|
1661
|
-
[
|
1726
|
+
['text/custom', 'text/custom; charset=UTF-8'].each do |content_type|
|
1662
1727
|
it "uses parser for #{content_type}" do
|
1663
|
-
put '/simple',
|
1728
|
+
put '/simple', 'simple', 'CONTENT_TYPE' => content_type
|
1664
1729
|
expect(last_response.status).to eq(200)
|
1665
|
-
expect(last_response.body).to eql
|
1730
|
+
expect(last_response.body).to eql 'elpmis'
|
1666
1731
|
end
|
1667
1732
|
end
|
1668
1733
|
end
|
@@ -1673,40 +1738,40 @@ describe Grape::API do
|
|
1673
1738
|
end
|
1674
1739
|
end
|
1675
1740
|
before :each do
|
1676
|
-
subject.content_type :txt,
|
1677
|
-
subject.content_type :custom,
|
1741
|
+
subject.content_type :txt, 'text/plain'
|
1742
|
+
subject.content_type :custom, 'text/custom'
|
1678
1743
|
subject.parser :custom, CustomParser
|
1679
1744
|
subject.put :simple do
|
1680
1745
|
params[:simple]
|
1681
1746
|
end
|
1682
1747
|
end
|
1683
1748
|
it 'uses custom parser' do
|
1684
|
-
put '/simple',
|
1749
|
+
put '/simple', 'simple', 'CONTENT_TYPE' => 'text/custom'
|
1685
1750
|
expect(last_response.status).to eq(200)
|
1686
|
-
expect(last_response.body).to eql
|
1751
|
+
expect(last_response.body).to eql 'elpmis'
|
1687
1752
|
end
|
1688
1753
|
end
|
1689
|
-
context
|
1754
|
+
context 'multi_xml' do
|
1690
1755
|
it "doesn't parse yaml" do
|
1691
1756
|
subject.put :yaml do
|
1692
1757
|
params[:tag]
|
1693
1758
|
end
|
1694
|
-
put '/yaml', '<tag type="symbol">a123</tag>',
|
1759
|
+
put '/yaml', '<tag type="symbol">a123</tag>', 'CONTENT_TYPE' => 'application/xml'
|
1695
1760
|
expect(last_response.status).to eq(400)
|
1696
1761
|
expect(last_response.body).to eql 'Disallowed type attribute: "symbol"'
|
1697
1762
|
end
|
1698
1763
|
end
|
1699
|
-
context
|
1764
|
+
context 'none parser class' do
|
1700
1765
|
before :each do
|
1701
1766
|
subject.parser :json, nil
|
1702
|
-
subject.put
|
1767
|
+
subject.put 'data' do
|
1703
1768
|
"body: #{env['api.request.body'] }"
|
1704
1769
|
end
|
1705
1770
|
end
|
1706
|
-
it
|
1707
|
-
put '/data', 'not valid json',
|
1771
|
+
it 'does not parse data' do
|
1772
|
+
put '/data', 'not valid json', 'CONTENT_TYPE' => 'application/json'
|
1708
1773
|
expect(last_response.status).to eq(200)
|
1709
|
-
expect(last_response.body).to eq(
|
1774
|
+
expect(last_response.body).to eq('body: not valid json')
|
1710
1775
|
end
|
1711
1776
|
end
|
1712
1777
|
end
|
@@ -1720,7 +1785,7 @@ describe Grape::API do
|
|
1720
1785
|
subject.get '/data' do
|
1721
1786
|
{ x: 42 }
|
1722
1787
|
end
|
1723
|
-
get
|
1788
|
+
get '/data'
|
1724
1789
|
expect(last_response.status).to eq(200)
|
1725
1790
|
expect(last_response.body).to eq('{"x":42}')
|
1726
1791
|
end
|
@@ -1728,7 +1793,7 @@ describe Grape::API do
|
|
1728
1793
|
subject.post '/data' do
|
1729
1794
|
{ x: params[:x] }
|
1730
1795
|
end
|
1731
|
-
post
|
1796
|
+
post '/data', '{"x":42}', 'CONTENT_TYPE' => ''
|
1732
1797
|
expect(last_response.status).to eq(201)
|
1733
1798
|
expect(last_response.body).to eq('{"x":42}')
|
1734
1799
|
end
|
@@ -1739,7 +1804,7 @@ describe Grape::API do
|
|
1739
1804
|
subject.rescue_from :all
|
1740
1805
|
subject.default_error_status 200
|
1741
1806
|
subject.get '/exception' do
|
1742
|
-
|
1807
|
+
fail 'rain!'
|
1743
1808
|
end
|
1744
1809
|
get '/exception'
|
1745
1810
|
expect(last_response.status).to eql 200
|
@@ -1747,7 +1812,7 @@ describe Grape::API do
|
|
1747
1812
|
it 'has a default error status' do
|
1748
1813
|
subject.rescue_from :all
|
1749
1814
|
subject.get '/exception' do
|
1750
|
-
|
1815
|
+
fail 'rain!'
|
1751
1816
|
end
|
1752
1817
|
get '/exception'
|
1753
1818
|
expect(last_response.status).to eql 500
|
@@ -1756,7 +1821,7 @@ describe Grape::API do
|
|
1756
1821
|
subject.rescue_from :all
|
1757
1822
|
subject.default_error_status 400
|
1758
1823
|
subject.get '/exception' do
|
1759
|
-
error!
|
1824
|
+
error! 'rain!'
|
1760
1825
|
end
|
1761
1826
|
get '/exception'
|
1762
1827
|
expect(last_response.status).to eql 400
|
@@ -1817,8 +1882,8 @@ describe Grape::API do
|
|
1817
1882
|
expect(subject.routes.size).to eq(1)
|
1818
1883
|
route = subject.routes[0]
|
1819
1884
|
expect(route.route_version).to be_nil
|
1820
|
-
expect(route.route_path).to eq(
|
1821
|
-
expect(route.route_method).to eq(
|
1885
|
+
expect(route.route_path).to eq('/ping(.:format)')
|
1886
|
+
expect(route.route_method).to eq('GET')
|
1822
1887
|
end
|
1823
1888
|
end
|
1824
1889
|
describe 'api structure with two versions and a namespace' do
|
@@ -1842,19 +1907,19 @@ describe Grape::API do
|
|
1842
1907
|
expect(subject.version).to eq('v2')
|
1843
1908
|
end
|
1844
1909
|
it 'returns versions' do
|
1845
|
-
expect(subject.versions).to eq(
|
1910
|
+
expect(subject.versions).to eq(%w(v1 v2))
|
1846
1911
|
end
|
1847
1912
|
it 'sets route paths' do
|
1848
1913
|
expect(subject.routes.size).to be >= 2
|
1849
|
-
expect(subject.routes[0].route_path).to eq(
|
1850
|
-
expect(subject.routes[1].route_path).to eq(
|
1914
|
+
expect(subject.routes[0].route_path).to eq('/:version/version(.:format)')
|
1915
|
+
expect(subject.routes[1].route_path).to eq('/p/:version/n1/n2/version(.:format)')
|
1851
1916
|
end
|
1852
1917
|
it 'sets route versions' do
|
1853
1918
|
expect(subject.routes[0].route_version).to eq('v1')
|
1854
1919
|
expect(subject.routes[1].route_version).to eq('v2')
|
1855
1920
|
end
|
1856
1921
|
it 'sets a nested namespace' do
|
1857
|
-
expect(subject.routes[1].route_namespace).to eq(
|
1922
|
+
expect(subject.routes[1].route_namespace).to eq('/n1/n2')
|
1858
1923
|
end
|
1859
1924
|
it 'sets prefix' do
|
1860
1925
|
expect(subject.routes[1].route_prefix).to eq('p')
|
@@ -1862,24 +1927,108 @@ describe Grape::API do
|
|
1862
1927
|
end
|
1863
1928
|
describe 'api structure with additional parameters' do
|
1864
1929
|
before(:each) do
|
1865
|
-
subject.
|
1930
|
+
subject.params do
|
1931
|
+
requires :token, desc: 'a token'
|
1932
|
+
optional :limit, desc: 'the limit'
|
1933
|
+
end
|
1934
|
+
subject.get 'split/:string' do
|
1866
1935
|
params[:string].split(params[:token], (params[:limit] || 0).to_i)
|
1867
1936
|
end
|
1868
1937
|
end
|
1869
1938
|
it 'splits a string' do
|
1870
|
-
get
|
1939
|
+
get '/split/a,b,c.json', token: ','
|
1871
1940
|
expect(last_response.body).to eq('["a","b","c"]')
|
1872
1941
|
end
|
1873
1942
|
it 'splits a string with limit' do
|
1874
|
-
get
|
1943
|
+
get '/split/a,b,c.json', token: ',', limit: '2'
|
1875
1944
|
expect(last_response.body).to eq('["a","b,c"]')
|
1876
1945
|
end
|
1877
1946
|
it 'sets route_params' do
|
1878
1947
|
expect(subject.routes.map { |route|
|
1879
|
-
{ params: route.route_params
|
1948
|
+
{ params: route.route_params }
|
1880
1949
|
}).to eq [
|
1881
|
-
{
|
1882
|
-
|
1950
|
+
{
|
1951
|
+
params: {
|
1952
|
+
'string' => '',
|
1953
|
+
'token' => { required: true, desc: 'a token' },
|
1954
|
+
'limit' => { required: false, desc: 'the limit' }
|
1955
|
+
}
|
1956
|
+
}
|
1957
|
+
]
|
1958
|
+
end
|
1959
|
+
end
|
1960
|
+
describe 'api structure with multiple apis' do
|
1961
|
+
before(:each) do
|
1962
|
+
subject.params do
|
1963
|
+
requires :one, desc: 'a token'
|
1964
|
+
optional :two, desc: 'the limit'
|
1965
|
+
end
|
1966
|
+
subject.get 'one' do
|
1967
|
+
end
|
1968
|
+
|
1969
|
+
subject.params do
|
1970
|
+
requires :three, desc: 'a token'
|
1971
|
+
optional :four, desc: 'the limit'
|
1972
|
+
end
|
1973
|
+
subject.get 'two' do
|
1974
|
+
end
|
1975
|
+
end
|
1976
|
+
it 'sets route_params' do
|
1977
|
+
expect(subject.routes.map { |route|
|
1978
|
+
{ params: route.route_params }
|
1979
|
+
}).to eq [
|
1980
|
+
{
|
1981
|
+
params: {
|
1982
|
+
'one' => { required: true, desc: 'a token' },
|
1983
|
+
'two' => { required: false, desc: 'the limit' }
|
1984
|
+
}
|
1985
|
+
},
|
1986
|
+
{
|
1987
|
+
params: {
|
1988
|
+
'three' => { required: true, desc: 'a token' },
|
1989
|
+
'four' => { required: false, desc: 'the limit' }
|
1990
|
+
}
|
1991
|
+
}
|
1992
|
+
]
|
1993
|
+
end
|
1994
|
+
end
|
1995
|
+
describe 'api structure with an api without params' do
|
1996
|
+
before(:each) do
|
1997
|
+
subject.params do
|
1998
|
+
requires :one, desc: 'a token'
|
1999
|
+
optional :two, desc: 'the limit'
|
2000
|
+
end
|
2001
|
+
subject.get 'one' do
|
2002
|
+
end
|
2003
|
+
|
2004
|
+
subject.get 'two' do
|
2005
|
+
end
|
2006
|
+
end
|
2007
|
+
it 'sets route_params' do
|
2008
|
+
expect(subject.routes.map { |route|
|
2009
|
+
{ params: route.route_params }
|
2010
|
+
}).to eq [
|
2011
|
+
{
|
2012
|
+
params: {
|
2013
|
+
'one' => { required: true, desc: 'a token' },
|
2014
|
+
'two' => { required: false, desc: 'the limit' }
|
2015
|
+
}
|
2016
|
+
},
|
2017
|
+
{
|
2018
|
+
params: {}
|
2019
|
+
}
|
2020
|
+
]
|
2021
|
+
end
|
2022
|
+
end
|
2023
|
+
describe 'api with a custom route setting' do
|
2024
|
+
before(:each) do
|
2025
|
+
subject.route_setting :custom, key: 'value'
|
2026
|
+
subject.get 'one'
|
2027
|
+
end
|
2028
|
+
it 'exposed' do
|
2029
|
+
expect(subject.routes.count).to eq 1
|
2030
|
+
route = subject.routes.first
|
2031
|
+
expect(route.route_settings[:custom]).to eq(key: 'value')
|
1883
2032
|
end
|
1884
2033
|
end
|
1885
2034
|
end
|
@@ -1889,112 +2038,114 @@ describe Grape::API do
|
|
1889
2038
|
expect(subject.routes).to eq([])
|
1890
2039
|
end
|
1891
2040
|
it 'empty array of routes' do
|
1892
|
-
subject.desc
|
2041
|
+
subject.desc 'grape api'
|
1893
2042
|
expect(subject.routes).to eq([])
|
1894
2043
|
end
|
1895
2044
|
it 'describes a method' do
|
1896
|
-
subject.desc
|
2045
|
+
subject.desc 'first method'
|
1897
2046
|
subject.get :first do ; end
|
1898
2047
|
expect(subject.routes.length).to eq(1)
|
1899
2048
|
route = subject.routes.first
|
1900
|
-
expect(route.route_description).to eq(
|
2049
|
+
expect(route.route_description).to eq('first method')
|
1901
2050
|
expect(route.route_foo).to be_nil
|
1902
2051
|
expect(route.route_params).to eq({})
|
1903
2052
|
end
|
1904
2053
|
it 'describes methods separately' do
|
1905
|
-
subject.desc
|
2054
|
+
subject.desc 'first method'
|
1906
2055
|
subject.get :first do ; end
|
1907
|
-
subject.desc
|
2056
|
+
subject.desc 'second method'
|
1908
2057
|
subject.get :second do ; end
|
1909
2058
|
expect(subject.routes.count).to eq(2)
|
1910
2059
|
expect(subject.routes.map { |route|
|
1911
2060
|
{ description: route.route_description, params: route.route_params }
|
1912
2061
|
}).to eq [
|
1913
|
-
{ description:
|
1914
|
-
{ description:
|
2062
|
+
{ description: 'first method', params: {} },
|
2063
|
+
{ description: 'second method', params: {} }
|
1915
2064
|
]
|
1916
2065
|
end
|
1917
2066
|
it 'resets desc' do
|
1918
|
-
subject.desc
|
2067
|
+
subject.desc 'first method'
|
1919
2068
|
subject.get :first do ; end
|
1920
2069
|
subject.get :second do ; end
|
1921
2070
|
expect(subject.routes.map { |route|
|
1922
2071
|
{ description: route.route_description, params: route.route_params }
|
1923
2072
|
}).to eq [
|
1924
|
-
{ description:
|
2073
|
+
{ description: 'first method', params: {} },
|
1925
2074
|
{ description: nil, params: {} }
|
1926
2075
|
]
|
1927
2076
|
end
|
1928
2077
|
it 'namespaces and describe arbitrary parameters' do
|
1929
2078
|
subject.namespace 'ns' do
|
1930
|
-
desc
|
2079
|
+
desc 'ns second', foo: 'bar'
|
1931
2080
|
get 'second' do ; end
|
1932
2081
|
end
|
1933
2082
|
expect(subject.routes.map { |route|
|
1934
2083
|
{ description: route.route_description, foo: route.route_foo, params: route.route_params }
|
1935
2084
|
}).to eq [
|
1936
|
-
{ description:
|
2085
|
+
{ description: 'ns second', foo: 'bar', params: {} }
|
1937
2086
|
]
|
1938
2087
|
end
|
1939
2088
|
it 'includes details' do
|
1940
|
-
subject.desc
|
2089
|
+
subject.desc 'method', details: 'method details'
|
1941
2090
|
subject.get 'method' do ; end
|
1942
2091
|
expect(subject.routes.map { |route|
|
1943
2092
|
{ description: route.route_description, details: route.route_details, params: route.route_params }
|
1944
2093
|
}).to eq [
|
1945
|
-
{ description:
|
2094
|
+
{ description: 'method', details: 'method details', params: {} }
|
1946
2095
|
]
|
1947
2096
|
end
|
1948
2097
|
it 'describes a method with parameters' do
|
1949
|
-
subject.desc
|
2098
|
+
subject.desc 'Reverses a string.', params: { 's' => { desc: 'string to reverse', type: 'string' } }
|
1950
2099
|
subject.get 'reverse' do
|
1951
2100
|
params[:s].reverse
|
1952
2101
|
end
|
1953
2102
|
expect(subject.routes.map { |route|
|
1954
2103
|
{ description: route.route_description, params: route.route_params }
|
1955
2104
|
}).to eq [
|
1956
|
-
{ description:
|
2105
|
+
{ description: 'Reverses a string.', params: { 's' => { desc: 'string to reverse', type: 'string' } } }
|
1957
2106
|
]
|
1958
2107
|
end
|
1959
2108
|
it 'merges the parameters of the namespace with the parameters of the method' do
|
1960
|
-
subject.desc
|
2109
|
+
subject.desc 'namespace'
|
1961
2110
|
subject.params do
|
1962
|
-
requires :ns_param, desc:
|
2111
|
+
requires :ns_param, desc: 'namespace parameter'
|
1963
2112
|
end
|
1964
2113
|
subject.namespace 'ns' do
|
1965
|
-
desc
|
2114
|
+
desc 'method'
|
1966
2115
|
params do
|
1967
|
-
optional :method_param, desc:
|
2116
|
+
optional :method_param, desc: 'method parameter'
|
1968
2117
|
end
|
1969
2118
|
get 'method' do ; end
|
1970
2119
|
end
|
1971
|
-
|
2120
|
+
|
2121
|
+
routes_doc = subject.routes.map { |route|
|
1972
2122
|
{ description: route.route_description, params: route.route_params }
|
1973
|
-
}
|
1974
|
-
|
2123
|
+
}
|
2124
|
+
expect(routes_doc).to eq [
|
2125
|
+
{ description: 'method',
|
1975
2126
|
params: {
|
1976
|
-
|
1977
|
-
|
2127
|
+
'ns_param' => { required: true, desc: 'namespace parameter' },
|
2128
|
+
'method_param' => { required: false, desc: 'method parameter' }
|
1978
2129
|
}
|
1979
2130
|
}
|
1980
2131
|
]
|
1981
2132
|
end
|
1982
2133
|
it 'merges the parameters of nested namespaces' do
|
1983
|
-
subject.desc
|
2134
|
+
subject.desc 'ns1'
|
1984
2135
|
subject.params do
|
1985
|
-
optional :ns_param, desc:
|
1986
|
-
requires :ns1_param, desc:
|
2136
|
+
optional :ns_param, desc: 'ns param 1'
|
2137
|
+
requires :ns1_param, desc: 'ns1 param'
|
1987
2138
|
end
|
1988
2139
|
subject.namespace 'ns1' do
|
1989
|
-
desc
|
2140
|
+
desc 'ns2'
|
1990
2141
|
params do
|
1991
|
-
requires :ns_param, desc:
|
1992
|
-
requires :ns2_param, desc:
|
2142
|
+
requires :ns_param, desc: 'ns param 2'
|
2143
|
+
requires :ns2_param, desc: 'ns2 param'
|
1993
2144
|
end
|
1994
2145
|
namespace 'ns2' do
|
1995
|
-
desc
|
2146
|
+
desc 'method'
|
1996
2147
|
params do
|
1997
|
-
optional :method_param, desc:
|
2148
|
+
optional :method_param, desc: 'method param'
|
1998
2149
|
end
|
1999
2150
|
get 'method' do ; end
|
2000
2151
|
end
|
@@ -2002,58 +2153,56 @@ describe Grape::API do
|
|
2002
2153
|
expect(subject.routes.map { |route|
|
2003
2154
|
{ description: route.route_description, params: route.route_params }
|
2004
2155
|
}).to eq [
|
2005
|
-
{ description:
|
2156
|
+
{ description: 'method',
|
2006
2157
|
params: {
|
2007
|
-
|
2008
|
-
|
2009
|
-
|
2010
|
-
|
2158
|
+
'ns_param' => { required: true, desc: 'ns param 2' },
|
2159
|
+
'ns1_param' => { required: true, desc: 'ns1 param' },
|
2160
|
+
'ns2_param' => { required: true, desc: 'ns2 param' },
|
2161
|
+
'method_param' => { required: false, desc: 'method param' }
|
2011
2162
|
}
|
2012
2163
|
}
|
2013
2164
|
]
|
2014
2165
|
end
|
2015
|
-
it
|
2016
|
-
subject.desc
|
2166
|
+
it 'groups nested params and prevents overwriting of params with same name in different groups' do
|
2167
|
+
subject.desc 'method'
|
2017
2168
|
subject.params do
|
2018
2169
|
group :group1 do
|
2019
|
-
optional :param1, desc:
|
2020
|
-
requires :param2, desc:
|
2170
|
+
optional :param1, desc: 'group1 param1 desc'
|
2171
|
+
requires :param2, desc: 'group1 param2 desc'
|
2021
2172
|
end
|
2022
2173
|
group :group2 do
|
2023
|
-
optional :param1, desc:
|
2024
|
-
requires :param2, desc:
|
2174
|
+
optional :param1, desc: 'group2 param1 desc'
|
2175
|
+
requires :param2, desc: 'group2 param2 desc'
|
2025
2176
|
end
|
2026
2177
|
end
|
2027
|
-
subject.get
|
2178
|
+
subject.get 'method' do ; end
|
2028
2179
|
|
2029
|
-
expect(subject.routes.map {
|
2030
|
-
|
2031
|
-
|
2032
|
-
|
2033
|
-
|
2034
|
-
|
2035
|
-
|
2036
|
-
"group2[param1]" => { required: false, desc: "group2 param1 desc" },
|
2037
|
-
"group2[param2]" => { required: true, desc: "group2 param2 desc" }
|
2180
|
+
expect(subject.routes.map(&:route_params)).to eq [{
|
2181
|
+
'group1' => { required: true, type: 'Array' },
|
2182
|
+
'group1[param1]' => { required: false, desc: 'group1 param1 desc' },
|
2183
|
+
'group1[param2]' => { required: true, desc: 'group1 param2 desc' },
|
2184
|
+
'group2' => { required: true, type: 'Array' },
|
2185
|
+
'group2[param1]' => { required: false, desc: 'group2 param1 desc' },
|
2186
|
+
'group2[param2]' => { required: true, desc: 'group2 param2 desc' }
|
2038
2187
|
}]
|
2039
2188
|
end
|
2040
2189
|
it 'uses full name of parameters in nested groups' do
|
2041
|
-
subject.desc
|
2190
|
+
subject.desc 'nesting'
|
2042
2191
|
subject.params do
|
2043
|
-
requires :root_param, desc:
|
2192
|
+
requires :root_param, desc: 'root param'
|
2044
2193
|
group :nested do
|
2045
|
-
requires :nested_param, desc:
|
2194
|
+
requires :nested_param, desc: 'nested param'
|
2046
2195
|
end
|
2047
2196
|
end
|
2048
2197
|
subject.get 'method' do ; end
|
2049
2198
|
expect(subject.routes.map { |route|
|
2050
2199
|
{ description: route.route_description, params: route.route_params }
|
2051
2200
|
}).to eq [
|
2052
|
-
{ description:
|
2201
|
+
{ description: 'nesting',
|
2053
2202
|
params: {
|
2054
|
-
|
2055
|
-
|
2056
|
-
|
2203
|
+
'root_param' => { required: true, desc: 'root param' },
|
2204
|
+
'nested' => { required: true, type: 'Array' },
|
2205
|
+
'nested[nested_param]' => { required: true, desc: 'nested param' }
|
2057
2206
|
}
|
2058
2207
|
}
|
2059
2208
|
]
|
@@ -2067,30 +2216,30 @@ describe Grape::API do
|
|
2067
2216
|
end
|
2068
2217
|
it 'parses parameters when no description is given' do
|
2069
2218
|
subject.params do
|
2070
|
-
requires :one_param, desc:
|
2219
|
+
requires :one_param, desc: 'one param'
|
2071
2220
|
end
|
2072
2221
|
subject.get 'method' do ; end
|
2073
2222
|
expect(subject.routes.map { |route|
|
2074
2223
|
{ description: route.route_description, params: route.route_params }
|
2075
2224
|
}).to eq [
|
2076
|
-
{ description: nil, params: {
|
2225
|
+
{ description: nil, params: { 'one_param' => { required: true, desc: 'one param' } } }
|
2077
2226
|
]
|
2078
2227
|
end
|
2079
2228
|
it 'does not symbolize params' do
|
2080
|
-
subject.desc
|
2229
|
+
subject.desc 'Reverses a string.', params: { 's' => { desc: 'string to reverse', type: 'string' } }
|
2081
2230
|
subject.get 'reverse/:s' do
|
2082
2231
|
params[:s].reverse
|
2083
2232
|
end
|
2084
2233
|
expect(subject.routes.map { |route|
|
2085
2234
|
{ description: route.route_description, params: route.route_params }
|
2086
2235
|
}).to eq [
|
2087
|
-
{ description:
|
2236
|
+
{ description: 'Reverses a string.', params: { 's' => { desc: 'string to reverse', type: 'string' } } }
|
2088
2237
|
]
|
2089
2238
|
end
|
2090
2239
|
end
|
2091
2240
|
|
2092
2241
|
describe '.mount' do
|
2093
|
-
let(:mounted_app) {
|
2242
|
+
let(:mounted_app) { ->(env) { [200, {}, ['MOUNTED']] } }
|
2094
2243
|
|
2095
2244
|
context 'with a bare rack app' do
|
2096
2245
|
before do
|
@@ -2111,7 +2260,7 @@ describe Grape::API do
|
|
2111
2260
|
subject.mount lambda { |env|
|
2112
2261
|
headers = {}
|
2113
2262
|
headers['X-Cascade'] == 'pass' unless env['PATH_INFO'].include?('boo')
|
2114
|
-
[200, headers, [
|
2263
|
+
[200, headers, ['Farfegnugen']]
|
2115
2264
|
} => '/'
|
2116
2265
|
|
2117
2266
|
get '/boo'
|
@@ -2136,7 +2285,7 @@ describe Grape::API do
|
|
2136
2285
|
subject.namespace :cool do
|
2137
2286
|
app = Class.new(Grape::API)
|
2138
2287
|
app.get('/awesome') do
|
2139
|
-
|
2288
|
+
'yo'
|
2140
2289
|
end
|
2141
2290
|
|
2142
2291
|
mount app
|
@@ -2152,7 +2301,7 @@ describe Grape::API do
|
|
2152
2301
|
subject.namespace :cool do
|
2153
2302
|
inner_app = Class.new(Grape::API)
|
2154
2303
|
inner_app.get('/awesome') do
|
2155
|
-
|
2304
|
+
'yo'
|
2156
2305
|
end
|
2157
2306
|
|
2158
2307
|
app = Class.new(Grape::API)
|
@@ -2168,12 +2317,15 @@ describe Grape::API do
|
|
2168
2317
|
subject.rescue_from :all do |e|
|
2169
2318
|
rack_response("rescued from #{e.message}", 202)
|
2170
2319
|
end
|
2320
|
+
|
2321
|
+
app = Class.new(Grape::API)
|
2322
|
+
|
2171
2323
|
subject.namespace :mounted do
|
2172
|
-
app = Class.new(Grape::API)
|
2173
2324
|
app.rescue_from ArgumentError
|
2174
|
-
app.get('/fail') {
|
2325
|
+
app.get('/fail') { fail 'doh!' }
|
2175
2326
|
mount app
|
2176
2327
|
end
|
2328
|
+
|
2177
2329
|
get '/mounted/fail'
|
2178
2330
|
expect(last_response.status).to eql 202
|
2179
2331
|
expect(last_response.body).to eq('rescued from doh!')
|
@@ -2195,28 +2347,29 @@ describe Grape::API do
|
|
2195
2347
|
subject.namespace :cool do
|
2196
2348
|
app = Class.new(Grape::API)
|
2197
2349
|
app.get '/awesome' do
|
2198
|
-
|
2350
|
+
'sauce'
|
2199
2351
|
end
|
2200
2352
|
mount app => '/mounted'
|
2201
2353
|
end
|
2202
|
-
get
|
2354
|
+
get '/mounted/cool/awesome'
|
2203
2355
|
expect(last_response.status).to eq(200)
|
2204
|
-
expect(last_response.body).to eq(
|
2356
|
+
expect(last_response.body).to eq('sauce')
|
2205
2357
|
end
|
2206
2358
|
|
2207
2359
|
it 'mounts on a nested path' do
|
2208
|
-
|
2209
|
-
|
2210
|
-
|
2211
|
-
|
2360
|
+
APP1 = Class.new(Grape::API)
|
2361
|
+
APP2 = Class.new(Grape::API)
|
2362
|
+
APP2.get '/nice' do
|
2363
|
+
'play'
|
2212
2364
|
end
|
2213
2365
|
# note that the reverse won't work, mount from outside-in
|
2214
|
-
|
2215
|
-
|
2216
|
-
|
2366
|
+
APP3 = subject
|
2367
|
+
APP3.mount APP1 => '/app1'
|
2368
|
+
APP1.mount APP2 => '/app2'
|
2369
|
+
get '/app1/app2/nice'
|
2217
2370
|
expect(last_response.status).to eq(200)
|
2218
|
-
expect(last_response.body).to eq(
|
2219
|
-
options
|
2371
|
+
expect(last_response.body).to eq('play')
|
2372
|
+
options '/app1/app2/nice'
|
2220
2373
|
expect(last_response.status).to eq(204)
|
2221
2374
|
end
|
2222
2375
|
|
@@ -2233,6 +2386,7 @@ describe Grape::API do
|
|
2233
2386
|
subject.namespace :apples do
|
2234
2387
|
mount app
|
2235
2388
|
end
|
2389
|
+
|
2236
2390
|
get '/apples/colour'
|
2237
2391
|
expect(last_response.status).to eql 200
|
2238
2392
|
expect(last_response.body).to eq('red')
|
@@ -2250,7 +2404,7 @@ describe Grape::API do
|
|
2250
2404
|
subject.namespace :apples do
|
2251
2405
|
app = Class.new(Grape::API)
|
2252
2406
|
app.get('/colour') do
|
2253
|
-
|
2407
|
+
'red'
|
2254
2408
|
end
|
2255
2409
|
mount app
|
2256
2410
|
end
|
@@ -2261,7 +2415,6 @@ describe Grape::API do
|
|
2261
2415
|
options '/v1/apples/colour'
|
2262
2416
|
expect(last_response.status).to eql 204
|
2263
2417
|
end
|
2264
|
-
|
2265
2418
|
end
|
2266
2419
|
end
|
2267
2420
|
|
@@ -2289,7 +2442,7 @@ describe Grape::API do
|
|
2289
2442
|
end
|
2290
2443
|
end
|
2291
2444
|
|
2292
|
-
describe
|
2445
|
+
describe '.endpoint' do
|
2293
2446
|
before(:each) do
|
2294
2447
|
subject.format :json
|
2295
2448
|
subject.get '/endpoint/options' do
|
@@ -2302,9 +2455,9 @@ describe Grape::API do
|
|
2302
2455
|
it 'path' do
|
2303
2456
|
get '/endpoint/options'
|
2304
2457
|
options = MultiJson.load(last_response.body)
|
2305
|
-
expect(options[
|
2306
|
-
expect(options[
|
2307
|
-
expect(options[
|
2458
|
+
expect(options['path']).to eq(['/endpoint/options'])
|
2459
|
+
expect(options['source_location'][0]).to include 'api_spec.rb'
|
2460
|
+
expect(options['source_location'][1].to_i).to be > 0
|
2308
2461
|
end
|
2309
2462
|
end
|
2310
2463
|
|
@@ -2320,9 +2473,9 @@ describe Grape::API do
|
|
2320
2473
|
end
|
2321
2474
|
it 'provides access to route info' do
|
2322
2475
|
get '/'
|
2323
|
-
expect(last_response.body).to eq(
|
2476
|
+
expect(last_response.body).to eq('/(.:format)')
|
2324
2477
|
get '/path'
|
2325
|
-
expect(last_response.body).to eq(
|
2478
|
+
expect(last_response.body).to eq('/path(.:format)')
|
2326
2479
|
end
|
2327
2480
|
end
|
2328
2481
|
context 'with desc' do
|
@@ -2331,18 +2484,18 @@ describe Grape::API do
|
|
2331
2484
|
subject.get '/description' do
|
2332
2485
|
route.route_description
|
2333
2486
|
end
|
2334
|
-
subject.desc 'returns parameters', params: {
|
2487
|
+
subject.desc 'returns parameters', params: { 'x' => 'y' }
|
2335
2488
|
subject.get '/params/:id' do
|
2336
2489
|
route.route_params[params[:id]]
|
2337
2490
|
end
|
2338
2491
|
end
|
2339
2492
|
it 'returns route description' do
|
2340
2493
|
get '/description'
|
2341
|
-
expect(last_response.body).to eq(
|
2494
|
+
expect(last_response.body).to eq('returns description')
|
2342
2495
|
end
|
2343
2496
|
it 'returns route parameters' do
|
2344
2497
|
get '/params/x'
|
2345
|
-
expect(last_response.body).to eq(
|
2498
|
+
expect(last_response.body).to eq('y')
|
2346
2499
|
end
|
2347
2500
|
end
|
2348
2501
|
end
|
@@ -2350,7 +2503,7 @@ describe Grape::API do
|
|
2350
2503
|
context ':txt' do
|
2351
2504
|
before(:each) do
|
2352
2505
|
subject.format :txt
|
2353
|
-
subject.content_type :json,
|
2506
|
+
subject.content_type :json, 'application/json'
|
2354
2507
|
subject.get '/meaning_of_life' do
|
2355
2508
|
{ meaning_of_life: 42 }
|
2356
2509
|
end
|
@@ -2379,9 +2532,9 @@ describe Grape::API do
|
|
2379
2532
|
get '/meaning_of_life'
|
2380
2533
|
expect(last_response.body).to eq({ meaning_of_life: 42 }.to_s)
|
2381
2534
|
end
|
2382
|
-
it '
|
2535
|
+
it 'does not accept any extensions' do
|
2383
2536
|
get '/meaning_of_life.json'
|
2384
|
-
expect(last_response.
|
2537
|
+
expect(last_response.status).to eq(404)
|
2385
2538
|
end
|
2386
2539
|
it 'forces txt from a non-accepting header' do
|
2387
2540
|
get '/meaning_of_life', {}, 'HTTP_ACCEPT' => 'application/json'
|
@@ -2391,7 +2544,7 @@ describe Grape::API do
|
|
2391
2544
|
context ':json' do
|
2392
2545
|
before(:each) do
|
2393
2546
|
subject.format :json
|
2394
|
-
subject.content_type :txt,
|
2547
|
+
subject.content_type :txt, 'text/plain'
|
2395
2548
|
subject.get '/meaning_of_life' do
|
2396
2549
|
{ meaning_of_life: 42 }
|
2397
2550
|
end
|
@@ -2410,7 +2563,7 @@ describe Grape::API do
|
|
2410
2563
|
end
|
2411
2564
|
it 'can be overwritten with an explicit content type' do
|
2412
2565
|
subject.get '/meaning_of_life_with_content_type' do
|
2413
|
-
content_type
|
2566
|
+
content_type 'text/plain'
|
2414
2567
|
{ meaning_of_life: 42 }.to_s
|
2415
2568
|
end
|
2416
2569
|
get '/meaning_of_life_with_content_type'
|
@@ -2419,18 +2572,16 @@ describe Grape::API do
|
|
2419
2572
|
it 'raised :error from middleware' do
|
2420
2573
|
middleware = Class.new(Grape::Middleware::Base) do
|
2421
2574
|
def before
|
2422
|
-
throw :error, message:
|
2575
|
+
throw :error, message: 'Unauthorized', status: 42
|
2423
2576
|
end
|
2424
2577
|
end
|
2425
2578
|
subject.use middleware
|
2426
2579
|
subject.get do
|
2427
|
-
|
2428
2580
|
end
|
2429
|
-
get
|
2581
|
+
get '/'
|
2430
2582
|
expect(last_response.status).to eq(42)
|
2431
|
-
expect(last_response.body).to eq({ error:
|
2583
|
+
expect(last_response.body).to eq({ error: 'Unauthorized' }.to_json)
|
2432
2584
|
end
|
2433
|
-
|
2434
2585
|
end
|
2435
2586
|
context ':serializable_hash' do
|
2436
2587
|
before(:each) do
|
@@ -2450,7 +2601,7 @@ describe Grape::API do
|
|
2450
2601
|
end
|
2451
2602
|
it 'root' do
|
2452
2603
|
subject.get '/example' do
|
2453
|
-
{
|
2604
|
+
{ 'root' => SerializableHashExample.new }
|
2454
2605
|
end
|
2455
2606
|
get '/example'
|
2456
2607
|
expect(last_response.body).to eq('{"root":{"abc":"def"}}')
|
@@ -2463,13 +2614,13 @@ describe Grape::API do
|
|
2463
2614
|
expect(last_response.body).to eq('[{"abc":"def"},{"abc":"def"}]')
|
2464
2615
|
end
|
2465
2616
|
end
|
2466
|
-
context
|
2617
|
+
context ':xml' do
|
2467
2618
|
before(:each) do
|
2468
2619
|
subject.format :xml
|
2469
2620
|
end
|
2470
2621
|
it 'string' do
|
2471
|
-
subject.get
|
2472
|
-
|
2622
|
+
subject.get '/example' do
|
2623
|
+
'example'
|
2473
2624
|
end
|
2474
2625
|
get '/example'
|
2475
2626
|
expect(last_response.status).to eq(500)
|
@@ -2481,10 +2632,10 @@ describe Grape::API do
|
|
2481
2632
|
XML
|
2482
2633
|
end
|
2483
2634
|
it 'hash' do
|
2484
|
-
subject.get
|
2635
|
+
subject.get '/example' do
|
2485
2636
|
ActiveSupport::OrderedHash[
|
2486
|
-
:example1,
|
2487
|
-
:example2,
|
2637
|
+
:example1, 'example1',
|
2638
|
+
:example2, 'example2'
|
2488
2639
|
]
|
2489
2640
|
end
|
2490
2641
|
get '/example'
|
@@ -2498,8 +2649,8 @@ XML
|
|
2498
2649
|
XML
|
2499
2650
|
end
|
2500
2651
|
it 'array' do
|
2501
|
-
subject.get
|
2502
|
-
|
2652
|
+
subject.get '/example' do
|
2653
|
+
%w(example1 example2)
|
2503
2654
|
end
|
2504
2655
|
get '/example'
|
2505
2656
|
expect(last_response.status).to eq(200)
|
@@ -2514,14 +2665,13 @@ XML
|
|
2514
2665
|
it 'raised :error from middleware' do
|
2515
2666
|
middleware = Class.new(Grape::Middleware::Base) do
|
2516
2667
|
def before
|
2517
|
-
throw :error, message:
|
2668
|
+
throw :error, message: 'Unauthorized', status: 42
|
2518
2669
|
end
|
2519
2670
|
end
|
2520
2671
|
subject.use middleware
|
2521
2672
|
subject.get do
|
2522
|
-
|
2523
2673
|
end
|
2524
|
-
get
|
2674
|
+
get '/'
|
2525
2675
|
expect(last_response.status).to eq(42)
|
2526
2676
|
expect(last_response.body).to eq <<-XML
|
2527
2677
|
<?xml version="1.0" encoding="UTF-8"?>
|
@@ -2533,17 +2683,17 @@ XML
|
|
2533
2683
|
end
|
2534
2684
|
end
|
2535
2685
|
|
2536
|
-
context
|
2686
|
+
context 'catch-all' do
|
2537
2687
|
before do
|
2538
2688
|
api1 = Class.new(Grape::API)
|
2539
2689
|
api1.version 'v1', using: :path
|
2540
|
-
api1.get
|
2541
|
-
|
2690
|
+
api1.get 'hello' do
|
2691
|
+
'v1'
|
2542
2692
|
end
|
2543
2693
|
api2 = Class.new(Grape::API)
|
2544
2694
|
api2.version 'v2', using: :path
|
2545
|
-
api2.get
|
2546
|
-
|
2695
|
+
api2.get 'hello' do
|
2696
|
+
'v2'
|
2547
2697
|
end
|
2548
2698
|
subject.mount api1
|
2549
2699
|
subject.mount api2
|
@@ -2553,53 +2703,53 @@ XML
|
|
2553
2703
|
subject.route :any, '*path', anchor: anchor do
|
2554
2704
|
error!("Unrecognized request path: #{params[:path] } - #{env['PATH_INFO'] }#{env['SCRIPT_NAME'] }", 404)
|
2555
2705
|
end
|
2556
|
-
get
|
2706
|
+
get '/v1/hello'
|
2557
2707
|
expect(last_response.status).to eq(200)
|
2558
|
-
expect(last_response.body).to eq(
|
2559
|
-
get
|
2708
|
+
expect(last_response.body).to eq('v1')
|
2709
|
+
get '/v2/hello'
|
2560
2710
|
expect(last_response.status).to eq(200)
|
2561
|
-
expect(last_response.body).to eq(
|
2562
|
-
get
|
2711
|
+
expect(last_response.body).to eq('v2')
|
2712
|
+
get '/foobar'
|
2563
2713
|
expect(last_response.status).to eq(404)
|
2564
|
-
expect(last_response.body).to eq(
|
2714
|
+
expect(last_response.body).to eq('Unrecognized request path: foobar - /foobar')
|
2565
2715
|
end
|
2566
2716
|
end
|
2567
2717
|
end
|
2568
2718
|
|
2569
|
-
context
|
2570
|
-
context
|
2571
|
-
it
|
2719
|
+
context 'cascading' do
|
2720
|
+
context 'via version' do
|
2721
|
+
it 'cascades' do
|
2572
2722
|
subject.version 'v1', using: :path, cascade: true
|
2573
|
-
get
|
2723
|
+
get '/v1/hello'
|
2574
2724
|
expect(last_response.status).to eq(404)
|
2575
|
-
expect(last_response.headers[
|
2725
|
+
expect(last_response.headers['X-Cascade']).to eq('pass')
|
2576
2726
|
end
|
2577
|
-
it
|
2727
|
+
it 'does not cascade' do
|
2578
2728
|
subject.version 'v2', using: :path, cascade: false
|
2579
|
-
get
|
2729
|
+
get '/v2/hello'
|
2580
2730
|
expect(last_response.status).to eq(404)
|
2581
|
-
expect(last_response.headers.keys).not_to include
|
2731
|
+
expect(last_response.headers.keys).not_to include 'X-Cascade'
|
2582
2732
|
end
|
2583
2733
|
end
|
2584
|
-
context
|
2585
|
-
it
|
2734
|
+
context 'via endpoint' do
|
2735
|
+
it 'cascades' do
|
2586
2736
|
subject.cascade true
|
2587
|
-
get
|
2737
|
+
get '/hello'
|
2588
2738
|
expect(last_response.status).to eq(404)
|
2589
|
-
expect(last_response.headers[
|
2739
|
+
expect(last_response.headers['X-Cascade']).to eq('pass')
|
2590
2740
|
end
|
2591
|
-
it
|
2741
|
+
it 'does not cascade' do
|
2592
2742
|
subject.cascade false
|
2593
|
-
get
|
2743
|
+
get '/hello'
|
2594
2744
|
expect(last_response.status).to eq(404)
|
2595
|
-
expect(last_response.headers.keys).not_to include
|
2745
|
+
expect(last_response.headers.keys).not_to include 'X-Cascade'
|
2596
2746
|
end
|
2597
2747
|
end
|
2598
2748
|
end
|
2599
2749
|
|
2600
2750
|
context 'with json default_error_formatter' do
|
2601
2751
|
it 'returns json error' do
|
2602
|
-
subject.content_type :json,
|
2752
|
+
subject.content_type :json, 'application/json'
|
2603
2753
|
subject.default_error_formatter :json
|
2604
2754
|
subject.get '/something' do
|
2605
2755
|
'foo'
|
@@ -2609,4 +2759,33 @@ XML
|
|
2609
2759
|
expect(last_response.body).to eq("{\"error\":\"The requested format 'txt' is not supported.\"}")
|
2610
2760
|
end
|
2611
2761
|
end
|
2762
|
+
|
2763
|
+
context 'body' do
|
2764
|
+
context 'false' do
|
2765
|
+
before do
|
2766
|
+
subject.get '/blank' do
|
2767
|
+
body false
|
2768
|
+
end
|
2769
|
+
end
|
2770
|
+
it 'returns blank body' do
|
2771
|
+
get '/blank'
|
2772
|
+
expect(last_response.status).to eq(204)
|
2773
|
+
expect(last_response.body).to be_blank
|
2774
|
+
end
|
2775
|
+
end
|
2776
|
+
context 'plain text' do
|
2777
|
+
before do
|
2778
|
+
subject.get '/text' do
|
2779
|
+
content_type 'text/plain'
|
2780
|
+
body 'Hello World'
|
2781
|
+
'ignored'
|
2782
|
+
end
|
2783
|
+
end
|
2784
|
+
it 'returns blank body' do
|
2785
|
+
get '/text'
|
2786
|
+
expect(last_response.status).to eq(200)
|
2787
|
+
expect(last_response.body).to eq 'Hello World'
|
2788
|
+
end
|
2789
|
+
end
|
2790
|
+
end
|
2612
2791
|
end
|