grape 1.3.0 → 1.5.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +119 -1
  3. data/LICENSE +1 -1
  4. data/README.md +123 -29
  5. data/UPGRADING.md +265 -39
  6. data/lib/grape/api/instance.rb +32 -31
  7. data/lib/grape/api.rb +5 -5
  8. data/lib/grape/content_types.rb +34 -0
  9. data/lib/grape/dsl/callbacks.rb +1 -1
  10. data/lib/grape/dsl/helpers.rb +2 -1
  11. data/lib/grape/dsl/inside_route.rb +77 -43
  12. data/lib/grape/dsl/parameters.rb +12 -8
  13. data/lib/grape/dsl/routing.rb +12 -11
  14. data/lib/grape/dsl/validations.rb +18 -1
  15. data/lib/grape/eager_load.rb +1 -1
  16. data/lib/grape/endpoint.rb +8 -6
  17. data/lib/grape/exceptions/base.rb +0 -4
  18. data/lib/grape/exceptions/validation.rb +1 -1
  19. data/lib/grape/exceptions/validation_errors.rb +12 -13
  20. data/lib/grape/http/headers.rb +26 -0
  21. data/lib/grape/middleware/auth/base.rb +3 -3
  22. data/lib/grape/middleware/base.rb +4 -5
  23. data/lib/grape/middleware/error.rb +11 -13
  24. data/lib/grape/middleware/formatter.rb +3 -3
  25. data/lib/grape/middleware/stack.rb +10 -2
  26. data/lib/grape/middleware/versioner/header.rb +4 -4
  27. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +2 -1
  28. data/lib/grape/middleware/versioner/path.rb +1 -1
  29. data/lib/grape/namespace.rb +12 -2
  30. data/lib/grape/path.rb +13 -3
  31. data/lib/grape/request.rb +13 -8
  32. data/lib/grape/router/attribute_translator.rb +26 -5
  33. data/lib/grape/router/pattern.rb +17 -16
  34. data/lib/grape/router/route.rb +5 -24
  35. data/lib/grape/router.rb +26 -30
  36. data/lib/grape/{serve_file → serve_stream}/file_body.rb +1 -1
  37. data/lib/grape/{serve_file → serve_stream}/sendfile_response.rb +1 -1
  38. data/lib/grape/{serve_file/file_response.rb → serve_stream/stream_response.rb} +8 -8
  39. data/lib/grape/util/base_inheritable.rb +15 -8
  40. data/lib/grape/util/cache.rb +20 -0
  41. data/lib/grape/util/lazy_object.rb +43 -0
  42. data/lib/grape/util/lazy_value.rb +1 -0
  43. data/lib/grape/util/reverse_stackable_values.rb +2 -0
  44. data/lib/grape/util/stackable_values.rb +7 -20
  45. data/lib/grape/validations/attributes_iterator.rb +8 -0
  46. data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
  47. data/lib/grape/validations/params_scope.rb +10 -8
  48. data/lib/grape/validations/single_attribute_iterator.rb +1 -1
  49. data/lib/grape/validations/types/array_coercer.rb +14 -5
  50. data/lib/grape/validations/types/build_coercer.rb +5 -8
  51. data/lib/grape/validations/types/custom_type_coercer.rb +16 -2
  52. data/lib/grape/validations/types/dry_type_coercer.rb +36 -1
  53. data/lib/grape/validations/types/file.rb +15 -12
  54. data/lib/grape/validations/types/invalid_value.rb +24 -0
  55. data/lib/grape/validations/types/json.rb +40 -36
  56. data/lib/grape/validations/types/primitive_coercer.rb +15 -6
  57. data/lib/grape/validations/types/set_coercer.rb +6 -4
  58. data/lib/grape/validations/types/variant_collection_coercer.rb +1 -1
  59. data/lib/grape/validations/types.rb +7 -9
  60. data/lib/grape/validations/validator_factory.rb +1 -1
  61. data/lib/grape/validations/validators/as.rb +1 -1
  62. data/lib/grape/validations/validators/base.rb +8 -8
  63. data/lib/grape/validations/validators/coerce.rb +11 -15
  64. data/lib/grape/validations/validators/default.rb +3 -5
  65. data/lib/grape/validations/validators/exactly_one_of.rb +4 -2
  66. data/lib/grape/validations/validators/except_values.rb +1 -1
  67. data/lib/grape/validations/validators/multiple_params_base.rb +2 -1
  68. data/lib/grape/validations/validators/regexp.rb +1 -1
  69. data/lib/grape/validations/validators/values.rb +1 -1
  70. data/lib/grape/version.rb +1 -1
  71. data/lib/grape.rb +5 -5
  72. data/spec/grape/api/instance_spec.rb +50 -0
  73. data/spec/grape/api_remount_spec.rb +9 -4
  74. data/spec/grape/api_spec.rb +82 -6
  75. data/spec/grape/dsl/inside_route_spec.rb +182 -33
  76. data/spec/grape/endpoint/declared_spec.rb +601 -0
  77. data/spec/grape/endpoint_spec.rb +0 -521
  78. data/spec/grape/entity_spec.rb +7 -1
  79. data/spec/grape/exceptions/validation_errors_spec.rb +2 -2
  80. data/spec/grape/integration/rack_sendfile_spec.rb +12 -8
  81. data/spec/grape/middleware/auth/strategies_spec.rb +1 -1
  82. data/spec/grape/middleware/error_spec.rb +1 -1
  83. data/spec/grape/middleware/formatter_spec.rb +3 -3
  84. data/spec/grape/middleware/stack_spec.rb +10 -0
  85. data/spec/grape/path_spec.rb +4 -4
  86. data/spec/grape/request_spec.rb +1 -1
  87. data/spec/grape/validations/instance_behaivour_spec.rb +1 -1
  88. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +13 -3
  89. data/spec/grape/validations/params_scope_spec.rb +26 -0
  90. data/spec/grape/validations/single_attribute_iterator_spec.rb +17 -6
  91. data/spec/grape/validations/types/array_coercer_spec.rb +35 -0
  92. data/spec/grape/validations/types/primitive_coercer_spec.rb +135 -0
  93. data/spec/grape/validations/types/set_coercer_spec.rb +34 -0
  94. data/spec/grape/validations/types_spec.rb +1 -1
  95. data/spec/grape/validations/validators/coerce_spec.rb +366 -86
  96. data/spec/grape/validations/validators/default_spec.rb +170 -0
  97. data/spec/grape/validations/validators/exactly_one_of_spec.rb +12 -12
  98. data/spec/grape/validations/validators/except_values_spec.rb +1 -0
  99. data/spec/grape/validations/validators/values_spec.rb +1 -1
  100. data/spec/grape/validations_spec.rb +298 -30
  101. data/spec/integration/eager_load/eager_load_spec.rb +15 -0
  102. data/spec/shared/versioning_examples.rb +20 -20
  103. data/spec/spec_helper.rb +3 -10
  104. data/spec/support/chunks.rb +14 -0
  105. data/spec/support/eager_load.rb +19 -0
  106. data/spec/support/versioned_helpers.rb +4 -6
  107. metadata +27 -10
  108. data/lib/grape/util/content_types.rb +0 -28
@@ -1,18 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'set'
4
- require_relative 'dry_type_coercer'
4
+ require_relative 'array_coercer'
5
5
 
6
6
  module Grape
7
7
  module Validations
8
8
  module Types
9
9
  # Takes the given array and converts it to a set. Every element of the set
10
10
  # is also coerced.
11
- class SetCoercer < DryTypeCoercer
11
+ class SetCoercer < ArrayCoercer
12
+ register_collection Set
13
+
12
14
  def initialize(type, strict = false)
13
15
  super
14
16
 
15
- @elem_coercer = PrimitiveCoercer.new(type.first, strict)
17
+ @coercer = nil
16
18
  end
17
19
 
18
20
  def call(value)
@@ -25,7 +27,7 @@ module Grape
25
27
 
26
28
  def coerce_elements(collection)
27
29
  collection.each_with_object(Set.new) do |elem, memo|
28
- coerced_elem = @elem_coercer.call(elem)
30
+ coerced_elem = elem_coercer.call(elem)
29
31
 
30
32
  return coerced_elem if coerced_elem.is_a?(InvalidValue)
31
33
 
@@ -33,7 +33,7 @@ module Grape
33
33
  # the coerced result, or an instance
34
34
  # of {InvalidValue} if the value could not be coerced.
35
35
  def call(value)
36
- return InvalidValue.new unless value.is_a? Array
36
+ return unless value.is_a? Array
37
37
 
38
38
  value =
39
39
  if @method
@@ -7,6 +7,7 @@ require_relative 'types/multiple_type_coercer'
7
7
  require_relative 'types/variant_collection_coercer'
8
8
  require_relative 'types/json'
9
9
  require_relative 'types/file'
10
+ require_relative 'types/invalid_value'
10
11
 
11
12
  module Grape
12
13
  module Validations
@@ -21,10 +22,6 @@ module Grape
21
22
  # and {Grape::Dsl::Parameters#optional}. The main
22
23
  # entry point for this process is {Types.build_coercer}.
23
24
  module Types
24
- # Instances of this class may be used as tokens to denote that
25
- # a parameter value could not be coerced.
26
- class InvalidValue; end
27
-
28
25
  # Types representing a single value, which are coerced.
29
26
  PRIMITIVES = [
30
27
  # Numerical
@@ -42,7 +39,6 @@ module Grape
42
39
  Grape::API::Boolean,
43
40
  String,
44
41
  Symbol,
45
- Rack::Multipart::UploadedFile,
46
42
  TrueClass,
47
43
  FalseClass
48
44
  ].freeze
@@ -54,8 +50,7 @@ module Grape
54
50
  Set
55
51
  ].freeze
56
52
 
57
- # Types for which Grape provides special coercion
58
- # and type-checking logic.
53
+ # Special custom types provided by Grape.
59
54
  SPECIAL = {
60
55
  JSON => Json,
61
56
  Array[JSON] => JsonArray,
@@ -130,7 +125,6 @@ module Grape
130
125
  !primitive?(type) &&
131
126
  !structure?(type) &&
132
127
  !multiple?(type) &&
133
- !special?(type) &&
134
128
  type.respond_to?(:parse) &&
135
129
  type.method(:parse).arity == 1
136
130
  end
@@ -143,7 +137,11 @@ module Grape
143
137
  def self.collection_of_custom?(type)
144
138
  (type.is_a?(Array) || type.is_a?(Set)) &&
145
139
  type.length == 1 &&
146
- custom?(type.first)
140
+ (custom?(type.first) || special?(type.first))
141
+ end
142
+
143
+ def self.map_special(type)
144
+ SPECIAL.fetch(type, type)
147
145
  end
148
146
  end
149
147
  end
@@ -8,7 +8,7 @@ module Grape
8
8
  options[:options],
9
9
  options[:required],
10
10
  options[:params_scope],
11
- options[:opts])
11
+ **options[:opts])
12
12
  end
13
13
  end
14
14
  end
@@ -3,7 +3,7 @@
3
3
  module Grape
4
4
  module Validations
5
5
  class AsValidator < Base
6
- def initialize(attrs, options, required, scope, opts = {})
6
+ def initialize(attrs, options, required, scope, **opts)
7
7
  @renamed_options = options
8
8
  super
9
9
  end
@@ -12,14 +12,15 @@ module Grape
12
12
  # @param options [Object] implementation-dependent Validator options
13
13
  # @param required [Boolean] attribute(s) are required or optional
14
14
  # @param scope [ParamsScope] parent scope for this Validator
15
- # @param opts [Hash] additional validation options
16
- def initialize(attrs, options, required, scope, opts = {})
15
+ # @param opts [Array] additional validation options
16
+ def initialize(attrs, options, required, scope, *opts)
17
17
  @attrs = Array(attrs)
18
18
  @option = options
19
19
  @required = required
20
20
  @scope = scope
21
- @fail_fast = opts[:fail_fast] || false
22
- @allow_blank = opts[:allow_blank] || false
21
+ opts = opts.any? ? opts.shift : {}
22
+ @fail_fast = opts.fetch(:fail_fast, false)
23
+ @allow_blank = opts.fetch(:allow_blank, false)
23
24
  end
24
25
 
25
26
  # Validates a given request.
@@ -43,13 +44,12 @@ module Grape
43
44
  # there may be more than one error per field
44
45
  array_errors = []
45
46
 
46
- attributes.each do |val, attr_name, empty_val|
47
+ attributes.each do |val, attr_name, empty_val, skip_value|
48
+ next if skip_value
47
49
  next if !@scope.required? && empty_val
48
50
  next unless @scope.meets_dependency?(val, params)
49
51
  begin
50
- if @required || val.respond_to?(:key?) && val.key?(attr_name)
51
- validate_param!(attr_name, val)
52
- end
52
+ validate_param!(attr_name, val) if @required || val.respond_to?(:key?) && val.key?(attr_name)
53
53
  rescue Grape::Exceptions::Validation => e
54
54
  array_errors << e
55
55
  end
@@ -17,7 +17,7 @@ module Grape
17
17
 
18
18
  module Validations
19
19
  class CoerceValidator < Base
20
- def initialize(*_args)
20
+ def initialize(attrs, options, required, scope, **opts)
21
21
  super
22
22
 
23
23
  @converter = if type.is_a?(Grape::Validations::Types::VariantCollectionCoercer)
@@ -36,7 +36,7 @@ module Grape
36
36
 
37
37
  new_value = coerce_value(params[attr_name])
38
38
 
39
- raise validation_exception(attr_name) unless valid_type?(new_value)
39
+ raise validation_exception(attr_name, new_value.message) unless valid_type?(new_value)
40
40
 
41
41
  # Don't assign a value if it is identical. It fixes a problem with Hashie::Mash
42
42
  # which looses wrappers for hashes and arrays after reassigning values
@@ -47,7 +47,9 @@ module Grape
47
47
  # h[:list] = list
48
48
  # h
49
49
  # => #<Hashie::Mash list=[1, 2, 3, 4]>
50
- params[attr_name] = new_value unless params[attr_name] == new_value
50
+ return if params[attr_name].class == new_value.class && params[attr_name] == new_value
51
+
52
+ params[attr_name] = new_value
51
53
  end
52
54
 
53
55
  private
@@ -65,18 +67,9 @@ 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
- rescue
72
+ rescue StandardError
80
73
  Types::InvalidValue.new
81
74
  end
82
75
 
@@ -87,8 +80,11 @@ module Grape
87
80
  @option[:type].is_a?(Hash) ? @option[:type][:value] : @option[:type]
88
81
  end
89
82
 
90
- def validation_exception(attr_name)
91
- Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message(:coerce))
83
+ def validation_exception(attr_name, custom_msg = nil)
84
+ Grape::Exceptions::Validation.new(
85
+ params: [@scope.full_name(attr_name)],
86
+ message: custom_msg || message(:coerce)
87
+ )
92
88
  end
93
89
  end
94
90
  end
@@ -3,13 +3,12 @@
3
3
  module Grape
4
4
  module Validations
5
5
  class DefaultValidator < Base
6
- def initialize(attrs, options, required, scope, opts = {})
6
+ def initialize(attrs, options, required, scope, **opts)
7
7
  @default = options
8
8
  super
9
9
  end
10
10
 
11
11
  def validate_param!(attr_name, params)
12
- return if params.key? attr_name
13
12
  params[attr_name] = if @default.is_a? Proc
14
13
  @default.call
15
14
  elsif @default.frozen? || !duplicatable?(@default)
@@ -22,9 +21,8 @@ module Grape
22
21
  def validate!(params)
23
22
  attrs = SingleAttributeIterator.new(self, @scope, params)
24
23
  attrs.each do |resource_params, attr_name|
25
- if resource_params.is_a?(Hash) && resource_params[attr_name].nil?
26
- validate_param!(attr_name, resource_params)
27
- end
24
+ next unless @scope.meets_dependency?(resource_params, params)
25
+ validate_param!(attr_name, resource_params) if resource_params.is_a?(Hash) && resource_params[attr_name].nil?
28
26
  end
29
27
  end
30
28
 
@@ -6,8 +6,10 @@ module Grape
6
6
  module Validations
7
7
  class ExactlyOneOfValidator < MultipleParamsBase
8
8
  def validate_params!(params)
9
- return if keys_in_common(params).length == 1
10
- raise Grape::Exceptions::Validation.new(params: all_keys, message: message(:exactly_one))
9
+ keys = keys_in_common(params)
10
+ return if keys.length == 1
11
+ raise Grape::Exceptions::Validation.new(params: all_keys, message: message(:exactly_one)) if keys.length.zero?
12
+ raise Grape::Exceptions::Validation.new(params: keys, message: message(:mutual_exclusion))
11
13
  end
12
14
  end
13
15
  end
@@ -3,7 +3,7 @@
3
3
  module Grape
4
4
  module Validations
5
5
  class ExceptValuesValidator < Base
6
- def initialize(attrs, options, required, scope, opts = {})
6
+ def initialize(attrs, options, required, scope, **opts)
7
7
  @except = options.is_a?(Hash) ? options[:value] : options
8
8
  super
9
9
  end
@@ -7,7 +7,8 @@ module Grape
7
7
  attributes = MultipleAttributesIterator.new(self, @scope, params)
8
8
  array_errors = []
9
9
 
10
- attributes.each do |resource_params|
10
+ attributes.each do |resource_params, skip_value|
11
+ next if skip_value
11
12
  begin
12
13
  validate_params!(resource_params)
13
14
  rescue Grape::Exceptions::Validation => e
@@ -5,7 +5,7 @@ module Grape
5
5
  class RegexpValidator < Base
6
6
  def validate_param!(attr_name, params)
7
7
  return unless params.respond_to?(:key?) && params.key?(attr_name)
8
- return if Array.wrap(params[attr_name]).all? { |param| param.nil? || (param.to_s =~ (options_key?(:value) ? @option[:value] : @option)) }
8
+ return if Array.wrap(params[attr_name]).all? { |param| param.nil? || param.to_s.match?((options_key?(:value) ? @option[:value] : @option)) }
9
9
  raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message(:regexp))
10
10
  end
11
11
  end
@@ -3,7 +3,7 @@
3
3
  module Grape
4
4
  module Validations
5
5
  class ValuesValidator < Base
6
- def initialize(attrs, options, required, scope, opts = {})
6
+ def initialize(attrs, options, required, scope, **opts)
7
7
  if options.is_a?(Hash)
8
8
  @excepts = options[:except]
9
9
  @values = options[:value]
data/lib/grape/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Grape
4
4
  # The current version of Grape.
5
- VERSION = '1.3.0'
5
+ VERSION = '1.5.2'
6
6
  end
data/lib/grape.rb CHANGED
@@ -12,6 +12,7 @@ require 'active_support/core_ext/hash/indifferent_access'
12
12
  require 'active_support/core_ext/object/blank'
13
13
  require 'active_support/core_ext/array/extract_options'
14
14
  require 'active_support/core_ext/array/wrap'
15
+ require 'active_support/core_ext/array/conversions'
15
16
  require 'active_support/core_ext/hash/deep_merge'
16
17
  require 'active_support/core_ext/hash/reverse_merge'
17
18
  require 'active_support/core_ext/hash/except'
@@ -20,7 +21,6 @@ require 'active_support/core_ext/hash/conversions'
20
21
  require 'active_support/dependencies/autoload'
21
22
  require 'active_support/notifications'
22
23
  require 'i18n'
23
- require 'thread'
24
24
 
25
25
  I18n.load_path << File.expand_path('../grape/locale/en.yml', __FILE__)
26
26
 
@@ -84,7 +84,6 @@ module Grape
84
84
  eager_autoload do
85
85
  autoload :DeepMergeableHash
86
86
  autoload :DeepSymbolizeHash
87
- autoload :DeepHashWithIndifferentAccess
88
87
  autoload :Hash
89
88
  end
90
89
  module ActiveSupport
@@ -208,18 +207,19 @@ module Grape
208
207
  end
209
208
  end
210
209
 
211
- module ServeFile
210
+ module ServeStream
212
211
  extend ::ActiveSupport::Autoload
213
212
  eager_autoload do
214
- autoload :FileResponse
215
213
  autoload :FileBody
216
214
  autoload :SendfileResponse
215
+ autoload :StreamResponse
217
216
  end
218
217
  end
219
218
  end
220
219
 
221
220
  require 'grape/config'
222
- require 'grape/util/content_types'
221
+ require 'grape/content_types'
222
+
223
223
  require 'grape/util/lazy_value'
224
224
  require 'grape/util/lazy_block'
225
225
  require 'grape/util/endpoint_configuration'
@@ -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
@@ -340,19 +340,24 @@ describe Grape::API do
340
340
  context 'when the configuration is read within a namespace' do
341
341
  before do
342
342
  a_remounted_api.namespace 'api' do
343
+ params do
344
+ requires configuration[:required_param]
345
+ end
343
346
  get "/#{configuration[:path]}" do
344
347
  '10 votes'
345
348
  end
346
349
  end
347
- root_api.mount a_remounted_api, with: { path: 'votes' }
348
- root_api.mount a_remounted_api, with: { path: 'scores' }
350
+ root_api.mount a_remounted_api, with: { path: 'votes', required_param: 'param_key' }
351
+ root_api.mount a_remounted_api, with: { path: 'scores', required_param: 'param_key' }
349
352
  end
350
353
 
351
354
  it 'will use the dynamic configuration on all routes' do
352
- get 'api/votes'
355
+ get 'api/votes', param_key: 'a'
353
356
  expect(last_response.body).to eql '10 votes'
354
- get 'api/scores'
357
+ get 'api/scores', param_key: 'a'
355
358
  expect(last_response.body).to eql '10 votes'
359
+ get 'api/votes'
360
+ expect(last_response.status).to eq 400
356
361
  end
357
362
  end
358
363
 
@@ -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
@@ -935,7 +1000,7 @@ XML
935
1000
 
936
1001
  it 'adds a before filter to current and child namespaces only' do
937
1002
  subject.get '/' do
938
- "root - #{@foo}"
1003
+ "root - #{instance_variable_defined?(:@foo) ? @foo : nil}"
939
1004
  end
940
1005
  subject.namespace :blah do
941
1006
  before { @foo = 'foo' }
@@ -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
@@ -3677,12 +3752,13 @@ XML
3677
3752
  end
3678
3753
  end
3679
3754
  context ':serializable_hash' do
3680
- before(:each) do
3681
- class SerializableHashExample
3682
- def serializable_hash
3683
- { abc: 'def' }
3684
- end
3755
+ class SerializableHashExample
3756
+ def serializable_hash
3757
+ { abc: 'def' }
3685
3758
  end
3759
+ end
3760
+
3761
+ before(:each) do
3686
3762
  subject.format :serializable_hash
3687
3763
  end
3688
3764
  it 'instance' do