grape-security 0.8.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.
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