grape-security 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (115) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +45 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +70 -0
  5. data/.travis.yml +18 -0
  6. data/.yardopts +2 -0
  7. data/CHANGELOG.md +314 -0
  8. data/CONTRIBUTING.md +118 -0
  9. data/Gemfile +21 -0
  10. data/Guardfile +14 -0
  11. data/LICENSE +20 -0
  12. data/README.md +1777 -0
  13. data/RELEASING.md +105 -0
  14. data/Rakefile +69 -0
  15. data/UPGRADING.md +124 -0
  16. data/grape-security.gemspec +39 -0
  17. data/grape.png +0 -0
  18. data/lib/grape.rb +99 -0
  19. data/lib/grape/api.rb +646 -0
  20. data/lib/grape/cookies.rb +39 -0
  21. data/lib/grape/endpoint.rb +533 -0
  22. data/lib/grape/error_formatter/base.rb +31 -0
  23. data/lib/grape/error_formatter/json.rb +15 -0
  24. data/lib/grape/error_formatter/txt.rb +16 -0
  25. data/lib/grape/error_formatter/xml.rb +15 -0
  26. data/lib/grape/exceptions/base.rb +66 -0
  27. data/lib/grape/exceptions/incompatible_option_values.rb +10 -0
  28. data/lib/grape/exceptions/invalid_formatter.rb +10 -0
  29. data/lib/grape/exceptions/invalid_versioner_option.rb +10 -0
  30. data/lib/grape/exceptions/invalid_with_option_for_represent.rb +10 -0
  31. data/lib/grape/exceptions/missing_mime_type.rb +10 -0
  32. data/lib/grape/exceptions/missing_option.rb +10 -0
  33. data/lib/grape/exceptions/missing_vendor_option.rb +10 -0
  34. data/lib/grape/exceptions/unknown_options.rb +10 -0
  35. data/lib/grape/exceptions/unknown_validator.rb +10 -0
  36. data/lib/grape/exceptions/validation.rb +26 -0
  37. data/lib/grape/exceptions/validation_errors.rb +43 -0
  38. data/lib/grape/formatter/base.rb +31 -0
  39. data/lib/grape/formatter/json.rb +12 -0
  40. data/lib/grape/formatter/serializable_hash.rb +35 -0
  41. data/lib/grape/formatter/txt.rb +11 -0
  42. data/lib/grape/formatter/xml.rb +12 -0
  43. data/lib/grape/http/request.rb +26 -0
  44. data/lib/grape/locale/en.yml +32 -0
  45. data/lib/grape/middleware/auth/base.rb +30 -0
  46. data/lib/grape/middleware/auth/basic.rb +13 -0
  47. data/lib/grape/middleware/auth/digest.rb +13 -0
  48. data/lib/grape/middleware/auth/oauth2.rb +83 -0
  49. data/lib/grape/middleware/base.rb +62 -0
  50. data/lib/grape/middleware/error.rb +89 -0
  51. data/lib/grape/middleware/filter.rb +17 -0
  52. data/lib/grape/middleware/formatter.rb +150 -0
  53. data/lib/grape/middleware/globals.rb +13 -0
  54. data/lib/grape/middleware/versioner.rb +32 -0
  55. data/lib/grape/middleware/versioner/accept_version_header.rb +67 -0
  56. data/lib/grape/middleware/versioner/header.rb +132 -0
  57. data/lib/grape/middleware/versioner/param.rb +42 -0
  58. data/lib/grape/middleware/versioner/path.rb +52 -0
  59. data/lib/grape/namespace.rb +23 -0
  60. data/lib/grape/parser/base.rb +29 -0
  61. data/lib/grape/parser/json.rb +11 -0
  62. data/lib/grape/parser/xml.rb +11 -0
  63. data/lib/grape/path.rb +70 -0
  64. data/lib/grape/route.rb +27 -0
  65. data/lib/grape/util/content_types.rb +18 -0
  66. data/lib/grape/util/deep_merge.rb +23 -0
  67. data/lib/grape/util/hash_stack.rb +120 -0
  68. data/lib/grape/validations.rb +322 -0
  69. data/lib/grape/validations/coerce.rb +63 -0
  70. data/lib/grape/validations/default.rb +25 -0
  71. data/lib/grape/validations/exactly_one_of.rb +26 -0
  72. data/lib/grape/validations/mutual_exclusion.rb +25 -0
  73. data/lib/grape/validations/presence.rb +16 -0
  74. data/lib/grape/validations/regexp.rb +12 -0
  75. data/lib/grape/validations/values.rb +23 -0
  76. data/lib/grape/version.rb +3 -0
  77. data/spec/grape/api_spec.rb +2571 -0
  78. data/spec/grape/endpoint_spec.rb +784 -0
  79. data/spec/grape/entity_spec.rb +324 -0
  80. data/spec/grape/exceptions/invalid_formatter_spec.rb +18 -0
  81. data/spec/grape/exceptions/invalid_versioner_option_spec.rb +18 -0
  82. data/spec/grape/exceptions/missing_mime_type_spec.rb +18 -0
  83. data/spec/grape/exceptions/missing_option_spec.rb +18 -0
  84. data/spec/grape/exceptions/unknown_options_spec.rb +18 -0
  85. data/spec/grape/exceptions/unknown_validator_spec.rb +18 -0
  86. data/spec/grape/exceptions/validation_errors_spec.rb +19 -0
  87. data/spec/grape/middleware/auth/basic_spec.rb +31 -0
  88. data/spec/grape/middleware/auth/digest_spec.rb +47 -0
  89. data/spec/grape/middleware/auth/oauth2_spec.rb +135 -0
  90. data/spec/grape/middleware/base_spec.rb +58 -0
  91. data/spec/grape/middleware/error_spec.rb +45 -0
  92. data/spec/grape/middleware/exception_spec.rb +184 -0
  93. data/spec/grape/middleware/formatter_spec.rb +258 -0
  94. data/spec/grape/middleware/versioner/accept_version_header_spec.rb +121 -0
  95. data/spec/grape/middleware/versioner/header_spec.rb +302 -0
  96. data/spec/grape/middleware/versioner/param_spec.rb +58 -0
  97. data/spec/grape/middleware/versioner/path_spec.rb +44 -0
  98. data/spec/grape/middleware/versioner_spec.rb +22 -0
  99. data/spec/grape/path_spec.rb +229 -0
  100. data/spec/grape/util/hash_stack_spec.rb +132 -0
  101. data/spec/grape/validations/coerce_spec.rb +208 -0
  102. data/spec/grape/validations/default_spec.rb +123 -0
  103. data/spec/grape/validations/exactly_one_of_spec.rb +71 -0
  104. data/spec/grape/validations/mutual_exclusion_spec.rb +61 -0
  105. data/spec/grape/validations/presence_spec.rb +142 -0
  106. data/spec/grape/validations/regexp_spec.rb +40 -0
  107. data/spec/grape/validations/values_spec.rb +152 -0
  108. data/spec/grape/validations/zh-CN.yml +10 -0
  109. data/spec/grape/validations_spec.rb +994 -0
  110. data/spec/shared/versioning_examples.rb +121 -0
  111. data/spec/spec_helper.rb +26 -0
  112. data/spec/support/basic_auth_encode_helpers.rb +3 -0
  113. data/spec/support/content_type_helpers.rb +11 -0
  114. data/spec/support/versioned_helpers.rb +50 -0
  115. metadata +421 -0
@@ -0,0 +1,58 @@
1
+ require 'spec_helper'
2
+
3
+ describe Grape::Middleware::Versioner::Param do
4
+
5
+ let(:app) { lambda { |env| [200, env, env['api.version']] } }
6
+ subject { Grape::Middleware::Versioner::Param.new(app, @options || {}) }
7
+
8
+ it 'sets the API version based on the default param (apiver)' do
9
+ env = Rack::MockRequest.env_for("/awesome", params: { "apiver" => "v1" })
10
+ expect(subject.call(env)[1]["api.version"]).to eq('v1')
11
+ end
12
+
13
+ it 'cuts (only) the version out of the params' do
14
+ env = Rack::MockRequest.env_for("/awesome", params: { "apiver" => "v1", "other_param" => "5" })
15
+ env['rack.request.query_hash'] = Rack::Utils.parse_nested_query(env['QUERY_STRING'])
16
+ expect(subject.call(env)[1]['rack.request.query_hash']["apiver"]).to be_nil
17
+ expect(subject.call(env)[1]['rack.request.query_hash']["other_param"]).to eq("5")
18
+ end
19
+
20
+ it 'provides a nil version if no version is given' do
21
+ env = Rack::MockRequest.env_for("/")
22
+ expect(subject.call(env).last).to be_nil
23
+ end
24
+
25
+ context 'with specified parameter name' do
26
+ before { @options = { parameter: 'v' } }
27
+ it 'sets the API version based on the custom parameter name' do
28
+ env = Rack::MockRequest.env_for("/awesome", params: { "v" => "v1" })
29
+ expect(subject.call(env)[1]["api.version"]).to eq("v1")
30
+ end
31
+ it 'does not set the API version based on the default param' do
32
+ env = Rack::MockRequest.env_for("/awesome", params: { "apiver" => "v1" })
33
+ expect(subject.call(env)[1]["api.version"]).to be_nil
34
+ end
35
+ end
36
+
37
+ context 'with specified versions' do
38
+ before { @options = { versions: ['v1', 'v2'] } }
39
+ it 'throws an error if a non-allowed version is specified' do
40
+ env = Rack::MockRequest.env_for("/awesome", params: { "apiver" => "v3" })
41
+ expect(catch(:error) { subject.call(env) }[:status]).to eq(404)
42
+ end
43
+ it 'allows versions that have been specified' do
44
+ env = Rack::MockRequest.env_for("/awesome", params: { "apiver" => "v1" })
45
+ expect(subject.call(env)[1]["api.version"]).to eq('v1')
46
+ end
47
+ end
48
+
49
+ it 'returns a 200 when no version is set (matches the first version found)' do
50
+ @options = {
51
+ versions: ['v1'],
52
+ version_options: { using: :header }
53
+ }
54
+ env = Rack::MockRequest.env_for("/awesome", params: {})
55
+ expect(subject.call(env).first).to eq(200)
56
+ end
57
+
58
+ end
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+
3
+ describe Grape::Middleware::Versioner::Path do
4
+ let(:app) { lambda { |env| [200, env, env['api.version']] } }
5
+ subject { Grape::Middleware::Versioner::Path.new(app, @options || {}) }
6
+
7
+ it 'sets the API version based on the first path' do
8
+ expect(subject.call('PATH_INFO' => '/v1/awesome').last).to eq('v1')
9
+ end
10
+
11
+ it 'does not cut the version out of the path' do
12
+ expect(subject.call('PATH_INFO' => '/v1/awesome')[1]['PATH_INFO']).to eq('/v1/awesome')
13
+ end
14
+
15
+ it 'provides a nil version if no path is given' do
16
+ expect(subject.call('PATH_INFO' => '/').last).to be_nil
17
+ end
18
+
19
+ context 'with a pattern' do
20
+ before { @options = { pattern: /v./i } }
21
+ it 'sets the version if it matches' do
22
+ expect(subject.call('PATH_INFO' => '/v1/awesome').last).to eq('v1')
23
+ end
24
+
25
+ it 'ignores the version if it fails to match' do
26
+ expect(subject.call('PATH_INFO' => '/awesome/radical').last).to be_nil
27
+ end
28
+ end
29
+
30
+ [['v1', 'v2'], [:v1, :v2], [:v1, 'v2'], ['v1', :v2]].each do |versions|
31
+ context 'with specified versions as #{versions}' do
32
+ before { @options = { versions: versions } }
33
+
34
+ it 'throws an error if a non-allowed version is specified' do
35
+ expect(catch(:error) { subject.call('PATH_INFO' => '/v3/awesome') }[:status]).to eq(404)
36
+ end
37
+
38
+ it 'allows versions that have been specified' do
39
+ expect(subject.call('PATH_INFO' => '/v1/asoasd').last).to eq('v1')
40
+ end
41
+ end
42
+ end
43
+
44
+ end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ describe Grape::Middleware::Versioner do
4
+
5
+ let(:klass) { Grape::Middleware::Versioner }
6
+
7
+ it 'recognizes :path' do
8
+ expect(klass.using(:path)).to eq(Grape::Middleware::Versioner::Path)
9
+ end
10
+
11
+ it 'recognizes :header' do
12
+ expect(klass.using(:header)).to eq(Grape::Middleware::Versioner::Header)
13
+ end
14
+
15
+ it 'recognizes :param' do
16
+ expect(klass.using(:param)).to eq(Grape::Middleware::Versioner::Param)
17
+ end
18
+
19
+ it 'recognizes :accept_version_header' do
20
+ expect(klass.using(:accept_version_header)).to eq(Grape::Middleware::Versioner::AcceptVersionHeader)
21
+ end
22
+ end
@@ -0,0 +1,229 @@
1
+ require 'spec_helper'
2
+
3
+ module Grape
4
+ describe Path do
5
+
6
+ describe "#initialize" do
7
+ it "remembers the path" do
8
+ path = Path.new('/:id', anything, anything)
9
+ expect(path.raw_path).to eql('/:id')
10
+ end
11
+
12
+ it "remembers the namespace" do
13
+ path = Path.new(anything, '/users', anything)
14
+ expect(path.namespace).to eql('/users')
15
+ end
16
+
17
+ it "remebers the settings" do
18
+ path = Path.new(anything, anything, foo: 'bar')
19
+ expect(path.settings).to eql(foo: 'bar')
20
+ end
21
+ end
22
+
23
+ describe "#mount_path" do
24
+ it "is nil when no mount path setting exists" do
25
+ path = Path.new(anything, anything, {})
26
+ expect(path.mount_path).to be_nil
27
+ end
28
+
29
+ it "is nil when the mount path is nil" do
30
+ path = Path.new(anything, anything, mount_path: nil)
31
+ expect(path.mount_path).to be_nil
32
+ end
33
+
34
+ it "splits the mount path" do
35
+ path = Path.new(anything, anything, mount_path: 'foo/bar')
36
+ expect(path.mount_path).to eql(['foo', 'bar'])
37
+ end
38
+ end
39
+
40
+ describe "#root_prefix" do
41
+ it "is nil when no root prefix setting exists" do
42
+ path = Path.new(anything, anything, {})
43
+ expect(path.root_prefix).to be_nil
44
+ end
45
+
46
+ it "is nil when the mount path is nil" do
47
+ path = Path.new(anything, anything, root_prefix: nil)
48
+ expect(path.root_prefix).to be_nil
49
+ end
50
+
51
+ it "splits the mount path" do
52
+ path = Path.new(anything, anything, root_prefix: 'hello/world')
53
+ expect(path.root_prefix).to eql(['hello', 'world'])
54
+ end
55
+ end
56
+
57
+ describe "#uses_path_versioning?" do
58
+ it "is false when the version setting is nil" do
59
+ path = Path.new(anything, anything, version: nil)
60
+ expect(path.uses_path_versioning?).to be false
61
+ end
62
+
63
+ it "is false when the version option is header" do
64
+ path = Path.new(
65
+ anything,
66
+ anything,
67
+ version: 'v1',
68
+ version_options: { using: :header }
69
+ )
70
+
71
+ expect(path.uses_path_versioning?).to be false
72
+ end
73
+
74
+ it "is true when the version option is path" do
75
+ path = Path.new(
76
+ anything,
77
+ anything,
78
+ version: 'v1',
79
+ version_options: { using: :path }
80
+ )
81
+
82
+ expect(path.uses_path_versioning?).to be true
83
+ end
84
+ end
85
+
86
+ describe "#has_namespace?" do
87
+ it "is false when the namespace is nil" do
88
+ path = Path.new(anything, nil, anything)
89
+ expect(path).not_to have_namespace
90
+ end
91
+
92
+ it "is false when the namespace starts with whitespace" do
93
+ path = Path.new(anything, ' /foo', anything)
94
+ expect(path).not_to have_namespace
95
+ end
96
+
97
+ it "is false when the namespace is the root path" do
98
+ path = Path.new(anything, '/', anything)
99
+ expect(path).not_to have_namespace
100
+ end
101
+
102
+ it "is true otherwise" do
103
+ path = Path.new(anything, '/world', anything)
104
+ expect(path).to have_namespace
105
+ end
106
+ end
107
+
108
+ describe "#has_path?" do
109
+ it "is false when the path is nil" do
110
+ path = Path.new(nil, anything, anything)
111
+ expect(path).not_to have_path
112
+ end
113
+
114
+ it "is false when the path starts with whitespace" do
115
+ path = Path.new(' /foo', anything, anything)
116
+ expect(path).not_to have_path
117
+ end
118
+
119
+ it "is false when the path is the root path" do
120
+ path = Path.new('/', anything, anything)
121
+ expect(path).not_to have_path
122
+ end
123
+
124
+ it "is true otherwise" do
125
+ path = Path.new('/hello', anything, anything)
126
+ expect(path).to have_path
127
+ end
128
+ end
129
+
130
+ describe "#path" do
131
+ context "mount_path" do
132
+ it "is not included when it is nil" do
133
+ path = Path.new(nil, nil, mount_path: '/foo/bar')
134
+ expect(path.path).to eql '/foo/bar'
135
+ end
136
+
137
+ it "is included when it is not nil" do
138
+ path = Path.new(nil, nil, {})
139
+ expect(path.path).to eql('/')
140
+ end
141
+ end
142
+
143
+ context "root_prefix" do
144
+ it "is not included when it is nil" do
145
+ path = Path.new(nil, nil, {})
146
+ expect(path.path).to eql('/')
147
+ end
148
+
149
+ it "is included after the mount path" do
150
+ path = Path.new(
151
+ nil,
152
+ nil,
153
+ mount_path: '/foo',
154
+ root_prefix: '/hello'
155
+ )
156
+
157
+ expect(path.path).to eql('/foo/hello')
158
+ end
159
+ end
160
+
161
+ it "uses the namespace after the mount path and root prefix" do
162
+ path = Path.new(
163
+ nil,
164
+ 'namespace',
165
+ mount_path: '/foo',
166
+ root_prefix: '/hello'
167
+ )
168
+
169
+ expect(path.path).to eql('/foo/hello/namespace')
170
+ end
171
+
172
+ it "uses the raw path after the namespace" do
173
+ path = Path.new(
174
+ 'raw_path',
175
+ 'namespace',
176
+ mount_path: '/foo',
177
+ root_prefix: '/hello'
178
+ )
179
+
180
+ expect(path.path).to eql('/foo/hello/namespace/raw_path')
181
+ end
182
+ end
183
+
184
+ describe "#suffix" do
185
+ context "when path versioning is used" do
186
+ it "includes a '/'" do
187
+ path = Path.new(nil, nil, {})
188
+ allow(path).to receive(:uses_path_versioning?) { true }
189
+
190
+ expect(path.suffix).to eql('(/.:format)')
191
+ end
192
+ end
193
+
194
+ context "when path versioning is not used" do
195
+ it "does not include a '/' when the path has a namespace" do
196
+ path = Path.new(nil, 'namespace', {})
197
+ allow(path).to receive(:uses_path_versioning?) { true }
198
+
199
+ expect(path.suffix).to eql('(.:format)')
200
+ end
201
+
202
+ it "does not include a '/' when the path has a path" do
203
+ path = Path.new('/path', nil, {})
204
+ allow(path).to receive(:uses_path_versioning?) { true }
205
+
206
+ expect(path.suffix).to eql('(.:format)')
207
+ end
208
+
209
+ it "includes a '/' otherwise" do
210
+ path = Path.new(nil, nil, {})
211
+ allow(path).to receive(:uses_path_versioning?) { true }
212
+
213
+ expect(path.suffix).to eql('(/.:format)')
214
+ end
215
+ end
216
+ end
217
+
218
+ describe "#path_with_suffix" do
219
+ it "combines the path and suffix" do
220
+ path = Path.new(nil, nil, {})
221
+ allow(path).to receive(:path) { '/the/path' }
222
+ allow(path).to receive(:suffix) { 'suffix' }
223
+
224
+ expect(path.path_with_suffix).to eql('/the/pathsuffix')
225
+ end
226
+ end
227
+
228
+ end
229
+ end
@@ -0,0 +1,132 @@
1
+ require 'spec_helper'
2
+
3
+ describe Grape::Util::HashStack do
4
+
5
+ describe '#get' do
6
+ it 'finds the first available key' do
7
+ subject[:abc] = 123
8
+ subject.push(abc: 345)
9
+ expect(subject.get(:abc)).to eq(345)
10
+ end
11
+
12
+ it 'is nil if the key has not been set' do
13
+ expect(subject[:abc]).to be_nil
14
+ end
15
+ end
16
+
17
+ describe '#set' do
18
+ it 'sets a value on the highest frame' do
19
+ subject.push
20
+ subject.set(:abc, 123)
21
+ expect(subject.stack.last[:abc]).to eq(123)
22
+ end
23
+ end
24
+
25
+ describe '#imbue' do
26
+ it 'pushes a new value onto the end of an array' do
27
+ subject[:abc] = []
28
+ subject.imbue :abc, [123]
29
+ subject.imbue :abc, [456]
30
+ expect(subject[:abc]).to eq([123, 456])
31
+ end
32
+
33
+ it 'merges a hash that is passed' do
34
+ subject[:abc] = { foo: 'bar' }
35
+ subject.imbue :abc, baz: 'wich'
36
+ expect(subject[:abc]).to eq(foo: 'bar', baz: 'wich')
37
+ end
38
+
39
+ it 'sets the value if not a hash or array' do
40
+ subject.imbue :abc, 123
41
+ expect(subject[:abc]).to eq(123)
42
+ end
43
+
44
+ it 'is able to imbue an array without explicit setting' do
45
+ subject.imbue :arr, [1]
46
+ subject.imbue :arr, [2]
47
+ expect(subject[:arr]).to eq([1, 2])
48
+ end
49
+
50
+ it 'is able to imbue a hash without explicit setting' do
51
+ subject.imbue :hash, foo: 'bar'
52
+ subject.imbue :hash, baz: 'wich'
53
+ expect(subject[:hash]).to eq(foo: 'bar', baz: 'wich')
54
+ end
55
+ end
56
+
57
+ describe '#push' do
58
+ it 'returns a HashStack' do
59
+ expect(subject.push(Grape::Util::HashStack.new)).to be_kind_of(Grape::Util::HashStack)
60
+ end
61
+
62
+ it 'places the passed value on the top of the stack' do
63
+ subject.push(abc: 123)
64
+ expect(subject.stack).to eq([{}, { abc: 123 }])
65
+ end
66
+
67
+ it 'pushes an empty hash by default' do
68
+ subject[:abc] = 123
69
+ subject.push
70
+ expect(subject.stack).to eq([{ abc: 123 }, {}])
71
+ end
72
+ end
73
+
74
+ describe '#pop' do
75
+ it 'removes and return the top frame' do
76
+ subject.push(abc: 123)
77
+ expect(subject.pop).to eq(abc: 123)
78
+ expect(subject.stack.size).to eq(1)
79
+ end
80
+ end
81
+
82
+ describe '#peek' do
83
+ it 'returns the top frame without removing it' do
84
+ subject.push(abc: 123)
85
+ expect(subject.peek).to eq(abc: 123)
86
+ expect(subject.stack.size).to eq(2)
87
+ end
88
+ end
89
+
90
+ describe '#prepend' do
91
+ it 'returns a HashStack' do
92
+ expect(subject.prepend(Grape::Util::HashStack.new)).to be_kind_of(Grape::Util::HashStack)
93
+ end
94
+
95
+ it "prepends a HashStack's stack onto its own stack" do
96
+ other = Grape::Util::HashStack.new.push(abc: 123)
97
+ expect(subject.prepend(other).stack).to eq([{}, { abc: 123 }, {}])
98
+ end
99
+ end
100
+
101
+ describe '#concat' do
102
+ it 'returns a HashStack' do
103
+ expect(subject.concat(Grape::Util::HashStack.new)).to be_kind_of(Grape::Util::HashStack)
104
+ end
105
+
106
+ it "appends a HashStack's stack onto its own stack" do
107
+ other = Grape::Util::HashStack.new.push(abc: 123)
108
+ expect(subject.concat(other).stack).to eq([{}, {}, { abc: 123 }])
109
+ end
110
+ end
111
+
112
+ describe '#update' do
113
+ it 'merges! into the top frame' do
114
+ subject.update(abc: 123)
115
+ expect(subject.stack).to eq([{ abc: 123 }])
116
+ end
117
+
118
+ it 'returns a HashStack' do
119
+ expect(subject.update(abc: 123)).to be_kind_of(Grape::Util::HashStack)
120
+ end
121
+ end
122
+
123
+ describe '#clone' do
124
+ it 'performs a deep copy' do
125
+ subject[:abc] = 123
126
+ subject.push def: 234
127
+ clone = subject.clone
128
+ clone[:def] = 345
129
+ expect(subject[:def]).to eq(234)
130
+ end
131
+ end
132
+ end