grape 0.3.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of grape might be problematic. Click here for more details.
- checksums.yaml +15 -0
- data/.gitignore +8 -0
- data/.rubocop.yml +70 -0
- data/.travis.yml +7 -6
- data/CHANGELOG.md +134 -4
- data/CONTRIBUTING.md +118 -0
- data/Gemfile +5 -2
- data/README.md +551 -116
- data/RELEASING.md +105 -0
- data/Rakefile +29 -8
- data/UPGRADING.md +124 -0
- data/grape.gemspec +3 -3
- data/lib/grape/api.rb +207 -88
- data/lib/grape/cookies.rb +4 -8
- data/lib/grape/endpoint.rb +198 -144
- data/lib/grape/error_formatter/base.rb +5 -7
- data/lib/grape/error_formatter/json.rb +3 -5
- data/lib/grape/error_formatter/txt.rb +1 -3
- data/lib/grape/error_formatter/xml.rb +4 -6
- data/lib/grape/exceptions/base.rb +9 -9
- data/lib/grape/exceptions/incompatible_option_values.rb +10 -0
- data/lib/grape/exceptions/invalid_formatter.rb +1 -4
- data/lib/grape/exceptions/invalid_versioner_option.rb +1 -5
- data/lib/grape/exceptions/invalid_with_option_for_represent.rb +1 -6
- data/lib/grape/exceptions/missing_mime_type.rb +1 -5
- data/lib/grape/exceptions/missing_option.rb +1 -4
- data/lib/grape/exceptions/missing_vendor_option.rb +1 -4
- data/lib/grape/exceptions/unknown_options.rb +1 -5
- data/lib/grape/exceptions/unknown_validator.rb +1 -3
- data/lib/grape/exceptions/validation.rb +13 -3
- data/lib/grape/exceptions/validation_errors.rb +43 -0
- data/lib/grape/formatter/base.rb +5 -7
- data/lib/grape/formatter/json.rb +0 -3
- data/lib/grape/formatter/serializable_hash.rb +15 -15
- data/lib/grape/formatter/txt.rb +0 -2
- data/lib/grape/formatter/xml.rb +0 -2
- data/lib/grape/http/request.rb +26 -0
- data/lib/grape/locale/en.yml +8 -5
- data/lib/grape/middleware/auth/base.rb +30 -0
- data/lib/grape/middleware/auth/basic.rb +3 -20
- data/lib/grape/middleware/auth/digest.rb +2 -19
- data/lib/grape/middleware/auth/oauth2.rb +31 -24
- data/lib/grape/middleware/base.rb +7 -7
- data/lib/grape/middleware/error.rb +36 -22
- data/lib/grape/middleware/filter.rb +3 -3
- data/lib/grape/middleware/formatter.rb +99 -61
- data/lib/grape/middleware/globals.rb +13 -0
- data/lib/grape/middleware/versioner/accept_version_header.rb +67 -0
- data/lib/grape/middleware/versioner/header.rb +22 -16
- data/lib/grape/middleware/versioner/param.rb +9 -11
- data/lib/grape/middleware/versioner/path.rb +10 -13
- data/lib/grape/middleware/versioner.rb +3 -1
- data/lib/grape/namespace.rb +23 -0
- data/lib/grape/parser/base.rb +3 -5
- data/lib/grape/parser/json.rb +0 -2
- data/lib/grape/parser/xml.rb +0 -2
- data/lib/grape/path.rb +70 -0
- data/lib/grape/route.rb +10 -6
- data/lib/grape/util/content_types.rb +2 -1
- data/lib/grape/util/deep_merge.rb +5 -5
- data/lib/grape/util/hash_stack.rb +13 -2
- data/lib/grape/validations/coerce.rb +11 -10
- data/lib/grape/validations/default.rb +25 -0
- data/lib/grape/validations/presence.rb +7 -3
- data/lib/grape/validations/regexp.rb +2 -5
- data/lib/grape/validations/values.rb +17 -0
- data/lib/grape/validations.rb +161 -54
- data/lib/grape/version.rb +1 -1
- data/lib/grape.rb +19 -4
- data/spec/grape/api_spec.rb +897 -268
- data/spec/grape/endpoint_spec.rb +283 -66
- data/spec/grape/entity_spec.rb +132 -29
- data/spec/grape/exceptions/missing_mime_type_spec.rb +3 -9
- data/spec/grape/exceptions/validation_errors_spec.rb +19 -0
- data/spec/grape/middleware/auth/basic_spec.rb +8 -8
- data/spec/grape/middleware/auth/digest_spec.rb +5 -5
- data/spec/grape/middleware/auth/oauth2_spec.rb +81 -36
- data/spec/grape/middleware/base_spec.rb +8 -13
- data/spec/grape/middleware/error_spec.rb +13 -17
- data/spec/grape/middleware/exception_spec.rb +47 -27
- data/spec/grape/middleware/formatter_spec.rb +103 -41
- data/spec/grape/middleware/versioner/accept_version_header_spec.rb +121 -0
- data/spec/grape/middleware/versioner/header_spec.rb +76 -51
- data/spec/grape/middleware/versioner/param_spec.rb +18 -18
- data/spec/grape/middleware/versioner/path_spec.rb +6 -6
- data/spec/grape/middleware/versioner_spec.rb +5 -2
- data/spec/grape/path_spec.rb +229 -0
- data/spec/grape/util/hash_stack_spec.rb +31 -32
- data/spec/grape/validations/coerce_spec.rb +116 -51
- data/spec/grape/validations/default_spec.rb +123 -0
- data/spec/grape/validations/presence_spec.rb +42 -44
- data/spec/grape/validations/regexp_spec.rb +9 -9
- data/spec/grape/validations/values_spec.rb +138 -0
- data/spec/grape/validations/zh-CN.yml +4 -3
- data/spec/grape/validations_spec.rb +681 -48
- data/spec/shared/versioning_examples.rb +22 -6
- data/spec/spec_helper.rb +3 -2
- data/spec/support/basic_auth_encode_helpers.rb +0 -1
- data/spec/support/content_type_helpers.rb +11 -0
- data/spec/support/versioned_helpers.rb +13 -5
- metadata +34 -84
data/spec/grape/entity_spec.rb
CHANGED
@@ -3,13 +3,16 @@ require 'grape_entity'
|
|
3
3
|
|
4
4
|
describe Grape::Entity do
|
5
5
|
subject { Class.new(Grape::API) }
|
6
|
-
|
6
|
+
|
7
|
+
def app
|
8
|
+
subject
|
9
|
+
end
|
7
10
|
|
8
11
|
describe '#present' do
|
9
12
|
it 'sets the object as the body if no options are provided' do
|
10
13
|
subject.get '/example' do
|
11
|
-
present(
|
12
|
-
body.should == {:
|
14
|
+
present(abc: 'def')
|
15
|
+
body.should == { abc: 'def' }
|
13
16
|
end
|
14
17
|
get '/example'
|
15
18
|
end
|
@@ -18,16 +21,16 @@ describe Grape::Entity do
|
|
18
21
|
subject.get '/example' do
|
19
22
|
entity_mock = Object.new
|
20
23
|
entity_mock.should_receive(:represent)
|
21
|
-
present Object.new, :
|
24
|
+
present Object.new, with: entity_mock
|
22
25
|
end
|
23
26
|
get '/example'
|
24
27
|
end
|
25
28
|
|
26
29
|
it 'pulls a representation from the class options if it exists' do
|
27
30
|
entity = Class.new(Grape::Entity)
|
28
|
-
entity.stub
|
31
|
+
entity.stub(:represent).and_return("Hiya")
|
29
32
|
|
30
|
-
subject.represent Object, :
|
33
|
+
subject.represent Object, with: entity
|
31
34
|
subject.get '/example' do
|
32
35
|
present Object.new
|
33
36
|
end
|
@@ -37,25 +40,40 @@ describe Grape::Entity do
|
|
37
40
|
|
38
41
|
it 'pulls a representation from the class options if the presented object is a collection of objects' do
|
39
42
|
entity = Class.new(Grape::Entity)
|
40
|
-
entity.stub
|
43
|
+
entity.stub(:represent).and_return("Hiya")
|
41
44
|
|
42
|
-
class TestObject
|
45
|
+
class TestObject
|
46
|
+
end
|
43
47
|
|
44
|
-
|
48
|
+
class FakeCollection
|
49
|
+
def first
|
50
|
+
TestObject.new
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
subject.represent TestObject, with: entity
|
45
55
|
subject.get '/example' do
|
46
56
|
present [TestObject.new]
|
47
57
|
end
|
58
|
+
|
59
|
+
subject.get '/example2' do
|
60
|
+
present FakeCollection.new
|
61
|
+
end
|
62
|
+
|
48
63
|
get '/example'
|
49
64
|
last_response.body.should == "Hiya"
|
65
|
+
|
66
|
+
get '/example2'
|
67
|
+
last_response.body.should == "Hiya"
|
50
68
|
end
|
51
69
|
|
52
70
|
it 'pulls a representation from the class ancestor if it exists' do
|
53
71
|
entity = Class.new(Grape::Entity)
|
54
|
-
entity.stub
|
72
|
+
entity.stub(:represent).and_return("Hiya")
|
55
73
|
|
56
74
|
subclass = Class.new(Object)
|
57
75
|
|
58
|
-
subject.represent Object, :
|
76
|
+
subject.represent Object, with: entity
|
59
77
|
subject.get '/example' do
|
60
78
|
present subclass.new
|
61
79
|
end
|
@@ -66,7 +84,7 @@ describe Grape::Entity do
|
|
66
84
|
it 'automatically uses Klass::Entity if that exists' do
|
67
85
|
some_model = Class.new
|
68
86
|
entity = Class.new(Grape::Entity)
|
69
|
-
entity.stub
|
87
|
+
entity.stub(:represent).and_return("Auto-detect!")
|
70
88
|
|
71
89
|
some_model.const_set :Entity, entity
|
72
90
|
|
@@ -80,7 +98,7 @@ describe Grape::Entity do
|
|
80
98
|
it 'automatically uses Klass::Entity based on the first object in the collection being presented' do
|
81
99
|
some_model = Class.new
|
82
100
|
entity = Class.new(Grape::Entity)
|
83
|
-
entity.stub
|
101
|
+
entity.stub(:represent).and_return("Auto-detect!")
|
84
102
|
|
85
103
|
some_model.const_set :Entity, entity
|
86
104
|
|
@@ -91,15 +109,40 @@ describe Grape::Entity do
|
|
91
109
|
last_response.body.should == 'Auto-detect!'
|
92
110
|
end
|
93
111
|
|
112
|
+
it 'does not run autodetection for Entity when explicitely provided' do
|
113
|
+
entity = Class.new(Grape::Entity)
|
114
|
+
some_array = Array.new
|
115
|
+
|
116
|
+
subject.get '/example' do
|
117
|
+
present some_array, with: entity
|
118
|
+
end
|
119
|
+
|
120
|
+
some_array.should_not_receive(:first)
|
121
|
+
get '/example'
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'autodetection does not use Entity if it is not a presenter' do
|
125
|
+
some_model = Class.new
|
126
|
+
entity = Class.new
|
127
|
+
|
128
|
+
some_model.class.const_set :Entity, entity
|
129
|
+
|
130
|
+
subject.get '/example' do
|
131
|
+
present some_model
|
132
|
+
end
|
133
|
+
get '/example'
|
134
|
+
entity.should_not_receive(:represent)
|
135
|
+
end
|
136
|
+
|
94
137
|
it 'adds a root key to the output if one is given' do
|
95
138
|
subject.get '/example' do
|
96
|
-
present({:
|
97
|
-
body.should == {:
|
139
|
+
present({ abc: 'def' }, root: :root)
|
140
|
+
body.should == { root: { abc: 'def' } }
|
98
141
|
end
|
99
142
|
get '/example'
|
100
143
|
end
|
101
144
|
|
102
|
-
[
|
145
|
+
[:json, :serializable_hash].each do |format|
|
103
146
|
|
104
147
|
it 'presents with #{format}' do
|
105
148
|
entity = Class.new(Grape::Entity)
|
@@ -114,7 +157,7 @@ describe Grape::Entity do
|
|
114
157
|
@id = id
|
115
158
|
end
|
116
159
|
end
|
117
|
-
present c.new(1), :
|
160
|
+
present c.new(1), with: entity
|
118
161
|
end
|
119
162
|
|
120
163
|
get '/example'
|
@@ -135,8 +178,8 @@ describe Grape::Entity do
|
|
135
178
|
@id = id
|
136
179
|
end
|
137
180
|
end
|
138
|
-
examples = [
|
139
|
-
present examples, :
|
181
|
+
examples = [c.new(1), c.new(2)]
|
182
|
+
present examples, with: entity
|
140
183
|
end
|
141
184
|
|
142
185
|
get '/examples'
|
@@ -146,21 +189,21 @@ describe Grape::Entity do
|
|
146
189
|
|
147
190
|
end
|
148
191
|
|
149
|
-
it 'presents with xml' do
|
192
|
+
it 'presents with xml' do
|
150
193
|
entity = Class.new(Grape::Entity)
|
151
194
|
entity.root "examples", "example"
|
152
195
|
entity.expose :name
|
153
196
|
|
154
197
|
subject.format :xml
|
155
198
|
|
156
|
-
subject.get '/example' do
|
157
|
-
c = Class.new do
|
199
|
+
subject.get '/example' do
|
200
|
+
c = Class.new do
|
158
201
|
attr_reader :name
|
159
202
|
def initialize(args)
|
160
203
|
@name = args[:name] || "no name set"
|
161
204
|
end
|
162
205
|
end
|
163
|
-
present c.new(
|
206
|
+
present c.new(name: "johnnyiller"), with: entity
|
164
207
|
end
|
165
208
|
get '/example'
|
166
209
|
last_response.status.should == 200
|
@@ -175,27 +218,87 @@ describe Grape::Entity do
|
|
175
218
|
XML
|
176
219
|
end
|
177
220
|
|
178
|
-
it 'presents with json' do
|
221
|
+
it 'presents with json' do
|
179
222
|
entity = Class.new(Grape::Entity)
|
180
223
|
entity.root "examples", "example"
|
181
224
|
entity.expose :name
|
182
225
|
|
183
226
|
subject.format :json
|
184
227
|
|
185
|
-
subject.get '/example' do
|
186
|
-
c = Class.new do
|
228
|
+
subject.get '/example' do
|
229
|
+
c = Class.new do
|
187
230
|
attr_reader :name
|
188
231
|
def initialize(args)
|
189
232
|
@name = args[:name] || "no name set"
|
190
233
|
end
|
191
234
|
end
|
192
|
-
present c.new(
|
235
|
+
present c.new(name: "johnnyiller"), with: entity
|
193
236
|
end
|
194
237
|
get '/example'
|
195
238
|
last_response.status.should == 200
|
196
239
|
last_response.headers['Content-type'].should == "application/json"
|
197
240
|
last_response.body.should == '{"example":{"name":"johnnyiller"}}'
|
198
241
|
end
|
199
|
-
end
|
200
242
|
|
201
|
-
|
243
|
+
it 'presents with jsonp utilising Rack::JSONP' do
|
244
|
+
require 'rack/contrib'
|
245
|
+
|
246
|
+
# Include JSONP middleware
|
247
|
+
subject.use Rack::JSONP
|
248
|
+
|
249
|
+
entity = Class.new(Grape::Entity)
|
250
|
+
entity.root "examples", "example"
|
251
|
+
entity.expose :name
|
252
|
+
|
253
|
+
# Rack::JSONP expects a standard JSON response
|
254
|
+
subject.format :json
|
255
|
+
|
256
|
+
subject.get '/example' do
|
257
|
+
c = Class.new do
|
258
|
+
attr_reader :name
|
259
|
+
def initialize(args)
|
260
|
+
@name = args[:name] || "no name set"
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
present c.new(name: "johnnyiller"), with: entity
|
265
|
+
end
|
266
|
+
|
267
|
+
get '/example?callback=abcDef'
|
268
|
+
last_response.status.should == 200
|
269
|
+
last_response.headers['Content-type'].should == "application/javascript"
|
270
|
+
last_response.body.should == 'abcDef({"example":{"name":"johnnyiller"}})'
|
271
|
+
end
|
272
|
+
|
273
|
+
context "present with multiple entities" do
|
274
|
+
it "present with multiple entities using optional symbol" do
|
275
|
+
user = Class.new do
|
276
|
+
attr_reader :name
|
277
|
+
def initialize(args)
|
278
|
+
@name = args[:name] || "no name set"
|
279
|
+
end
|
280
|
+
end
|
281
|
+
user1 = user.new(name: 'user1')
|
282
|
+
user2 = user.new(name: 'user2')
|
283
|
+
|
284
|
+
entity = Class.new(Grape::Entity)
|
285
|
+
entity.expose :name
|
286
|
+
|
287
|
+
subject.format :json
|
288
|
+
subject.get '/example' do
|
289
|
+
present :page, 1
|
290
|
+
present :user1, user1, with: entity
|
291
|
+
present :user2, user2, with: entity
|
292
|
+
end
|
293
|
+
get '/example'
|
294
|
+
expect_response_json = {
|
295
|
+
"page" => 1,
|
296
|
+
"user1" => { "name" => "user1" },
|
297
|
+
"user2" => { "name" => "user2" }
|
298
|
+
}
|
299
|
+
JSON(last_response.body).should == expect_response_json
|
300
|
+
end
|
301
|
+
|
302
|
+
end
|
303
|
+
end
|
304
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
|
-
# encoding: utf-8
|
2
1
|
require 'spec_helper'
|
2
|
+
|
3
3
|
describe Grape::Exceptions::MissingMimeType do
|
4
4
|
describe "#message" do
|
5
5
|
|
@@ -8,17 +8,11 @@ describe Grape::Exceptions::MissingMimeType do
|
|
8
8
|
end
|
9
9
|
|
10
10
|
it "contains the problem in the message" do
|
11
|
-
error.message.should include
|
12
|
-
"missing mime type for new_json"
|
13
|
-
)
|
11
|
+
error.message.should include "missing mime type for new_json"
|
14
12
|
end
|
15
13
|
|
16
14
|
it "contains the resolution in the message" do
|
17
|
-
error.message.should include
|
18
|
-
"or add your own with content_type :new_json, 'application/new_json' "
|
19
|
-
)
|
15
|
+
error.message.should include "or add your own with content_type :new_json, 'application/new_json' "
|
20
16
|
end
|
21
17
|
end
|
22
|
-
|
23
|
-
|
24
18
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
describe Grape::Exceptions::ValidationErrors do
|
5
|
+
let(:validation_message) { "FooBar is invalid" }
|
6
|
+
let(:validation_error) { OpenStruct.new(param: validation_message) }
|
7
|
+
|
8
|
+
context "message" do
|
9
|
+
context "is not repeated" do
|
10
|
+
let(:error) do
|
11
|
+
described_class.new(errors: [validation_error, validation_error])
|
12
|
+
end
|
13
|
+
subject(:message) { error.message.split(',').map(&:strip) }
|
14
|
+
|
15
|
+
it { expect(message).to include validation_message }
|
16
|
+
it { expect(message.size).to eq 1 }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -6,26 +6,26 @@ describe Grape::Middleware::Auth::Basic do
|
|
6
6
|
def app
|
7
7
|
Rack::Builder.new do |b|
|
8
8
|
b.use Grape::Middleware::Error
|
9
|
-
b.use(Grape::Middleware::Auth::Basic) do |u,p|
|
9
|
+
b.use(Grape::Middleware::Auth::Basic) do |u, p|
|
10
10
|
u && p && u == p
|
11
11
|
end
|
12
|
-
b.run lambda{|env| [200, {}, ["Hello there."]]}
|
12
|
+
b.run lambda { |env| [200, {}, ["Hello there."]] }
|
13
13
|
end
|
14
14
|
end
|
15
|
-
|
15
|
+
|
16
16
|
it 'throws a 401 if no auth is given' do
|
17
|
-
@proc = lambda{ false }
|
17
|
+
@proc = lambda { false }
|
18
18
|
get '/whatever'
|
19
19
|
last_response.status.should == 401
|
20
20
|
end
|
21
|
-
|
21
|
+
|
22
22
|
it 'authenticates if given valid creds' do
|
23
|
-
get '/whatever', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('admin','admin')
|
23
|
+
get '/whatever', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('admin', 'admin')
|
24
24
|
last_response.status.should == 200
|
25
25
|
end
|
26
|
-
|
26
|
+
|
27
27
|
it 'throws a 401 is wrong auth is given' do
|
28
|
-
get '/whatever', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('admin','wrong')
|
28
|
+
get '/whatever', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('admin', 'wrong')
|
29
29
|
last_response.status.should == 401
|
30
30
|
end
|
31
31
|
end
|
@@ -9,12 +9,12 @@ RSpec::Matchers.define :be_challenge do
|
|
9
9
|
end
|
10
10
|
|
11
11
|
class Test < Grape::API
|
12
|
-
http_digest(
|
13
|
-
{'foo' => 'bar'}[username]
|
12
|
+
http_digest(realm: 'Test Api', opaque: 'secret') do |username|
|
13
|
+
{ 'foo' => 'bar' }[username]
|
14
14
|
end
|
15
15
|
|
16
16
|
get '/test' do
|
17
|
-
[{:
|
17
|
+
[{ hey: 'you' }, { there: 'bar' }, { foo: 'baz' }]
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
@@ -22,12 +22,12 @@ describe Grape::Middleware::Auth::Digest do
|
|
22
22
|
def app
|
23
23
|
Test
|
24
24
|
end
|
25
|
-
|
25
|
+
|
26
26
|
it 'is a digest authentication challenge' do
|
27
27
|
get '/test'
|
28
28
|
last_response.should be_challenge
|
29
29
|
end
|
30
|
-
|
30
|
+
|
31
31
|
it 'throws a 401 if no auth is given' do
|
32
32
|
get '/test'
|
33
33
|
last_response.status.should == 401
|
@@ -2,89 +2,134 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Grape::Middleware::Auth::OAuth2 do
|
4
4
|
class FakeToken
|
5
|
+
attr_accessor :token
|
6
|
+
|
5
7
|
def self.verify(token)
|
6
|
-
FakeToken.new(token) if %w(g e).include?(token[0..0])
|
8
|
+
FakeToken.new(token) if !!token && %w(g e).include?(token[0..0])
|
7
9
|
end
|
8
|
-
|
10
|
+
|
9
11
|
def initialize(token)
|
10
|
-
|
12
|
+
@token = token
|
11
13
|
end
|
12
|
-
|
14
|
+
|
13
15
|
def expired?
|
14
|
-
|
16
|
+
@token[0..0] == 'e'
|
15
17
|
end
|
16
|
-
|
18
|
+
|
17
19
|
def permission_for?(env)
|
18
20
|
env['PATH_INFO'] == '/forbidden' ? false : true
|
19
21
|
end
|
20
|
-
|
21
|
-
attr_accessor :token
|
22
22
|
end
|
23
23
|
|
24
24
|
def app
|
25
25
|
Rack::Builder.app do
|
26
|
-
use Grape::Middleware::Auth::OAuth2, :
|
27
|
-
run lambda{|env| [200, {}, [
|
26
|
+
use Grape::Middleware::Auth::OAuth2, token_class: 'FakeToken'
|
27
|
+
run lambda { |env| [200, {}, [(env['api.token'].token if env['api.token'])]] }
|
28
28
|
end
|
29
29
|
end
|
30
|
-
|
30
|
+
|
31
31
|
context 'with the token in the query string' do
|
32
32
|
context 'and a valid token' do
|
33
|
-
before { get '/awesome?
|
34
|
-
|
33
|
+
before { get '/awesome?access_token=g123' }
|
34
|
+
|
35
35
|
it 'sets env["api.token"]' do
|
36
36
|
last_response.body.should == 'g123'
|
37
37
|
end
|
38
38
|
end
|
39
|
-
|
39
|
+
|
40
40
|
context 'and an invalid token' do
|
41
41
|
before do
|
42
42
|
@err = catch :error do
|
43
|
-
get '/awesome?
|
43
|
+
get '/awesome?access_token=b123'
|
44
44
|
end
|
45
45
|
end
|
46
|
-
|
46
|
+
|
47
47
|
it 'throws an error' do
|
48
48
|
@err[:status].should == 401
|
49
49
|
end
|
50
|
-
|
50
|
+
|
51
51
|
it 'sets the WWW-Authenticate header in the response' do
|
52
|
-
@err[:headers]['WWW-Authenticate'].should == "OAuth realm='OAuth API', error='
|
52
|
+
@err[:headers]['WWW-Authenticate'].should == "OAuth realm='OAuth API', error='invalid_grant'"
|
53
53
|
end
|
54
54
|
end
|
55
55
|
end
|
56
|
-
|
56
|
+
|
57
57
|
context 'with an expired token' do
|
58
58
|
before do
|
59
59
|
@err = catch :error do
|
60
|
-
get '/awesome?
|
60
|
+
get '/awesome?access_token=e123'
|
61
61
|
end
|
62
62
|
end
|
63
|
-
|
64
|
-
it
|
65
|
-
|
63
|
+
|
64
|
+
it 'throws an error' do
|
65
|
+
@err[:status].should == 401
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'sets the WWW-Authenticate header in the response to error' do
|
69
|
+
@err[:headers]['WWW-Authenticate'].should == "OAuth realm='OAuth API', error='invalid_grant'"
|
70
|
+
end
|
66
71
|
end
|
67
|
-
|
68
|
-
%w(HTTP_AUTHORIZATION X_HTTP_AUTHORIZATION X-HTTP_AUTHORIZATION REDIRECT_X_HTTP_AUTHORIZATION).each do |head|
|
69
|
-
context
|
70
|
-
before
|
71
|
-
|
72
|
+
|
73
|
+
%w(HTTP_AUTHORIZATION X_HTTP_AUTHORIZATION X-HTTP_AUTHORIZATION REDIRECT_X_HTTP_AUTHORIZATION).each do |head|
|
74
|
+
context "with the token in the #{head} header" do
|
75
|
+
before do
|
76
|
+
get '/awesome', {}, head => 'OAuth g123'
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'sets env["api.token"]' do
|
80
|
+
last_response.body.should == 'g123'
|
81
|
+
end
|
72
82
|
end
|
73
83
|
end
|
74
|
-
|
84
|
+
|
75
85
|
context 'with the token in the POST body' do
|
76
|
-
before
|
77
|
-
|
86
|
+
before do
|
87
|
+
post '/awesome', 'access_token' => 'g123'
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'sets env["api.token"]' do
|
91
|
+
last_response.body.should == 'g123'
|
92
|
+
end
|
78
93
|
end
|
79
|
-
|
94
|
+
|
80
95
|
context 'when accessing something outside its scope' do
|
81
96
|
before do
|
82
97
|
@err = catch :error do
|
83
|
-
get '/forbidden?
|
98
|
+
get '/forbidden?access_token=g123'
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'throws an error' do
|
103
|
+
@err[:status].should == 403
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'sets the WWW-Authenticate header in the response to error' do
|
107
|
+
@err[:headers]['WWW-Authenticate'].should == "OAuth realm='OAuth API', error='insufficient_scope'"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
context 'when authorization is not required' do
|
112
|
+
def app
|
113
|
+
Rack::Builder.app do
|
114
|
+
use Grape::Middleware::Auth::OAuth2, token_class: 'FakeToken', required: false
|
115
|
+
run lambda { |env| [200, {}, [(env['api.token'].token if env['api.token'])]] }
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
context 'with no token' do
|
120
|
+
before { post '/awesome' }
|
121
|
+
|
122
|
+
it 'succeeds anyway' do
|
123
|
+
last_response.status.should == 200
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
context 'with a valid token' do
|
128
|
+
before { get '/awesome?access_token=g123' }
|
129
|
+
|
130
|
+
it 'sets env["api.token"]' do
|
131
|
+
last_response.body.should == 'g123'
|
84
132
|
end
|
85
133
|
end
|
86
|
-
|
87
|
-
it { @err[:headers]['WWW-Authenticate'].should == "OAuth realm='OAuth API', error='insufficient_scope'" }
|
88
|
-
it { @err[:status].should == 403 }
|
89
134
|
end
|
90
135
|
end
|
@@ -1,23 +1,18 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Grape::Middleware::Base do
|
4
|
-
subject { Grape::Middleware::Base.new(
|
5
|
-
let(:blank_app) { lambda{|_| [200, {}, 'Hi there.']} }
|
4
|
+
subject { Grape::Middleware::Base.new(blank_app) }
|
5
|
+
let(:blank_app) { lambda { |_| [200, {}, 'Hi there.'] } }
|
6
6
|
|
7
7
|
before do
|
8
8
|
# Keep it one object for testing.
|
9
|
-
subject.stub
|
9
|
+
subject.stub(:dup).and_return(subject)
|
10
10
|
end
|
11
11
|
|
12
12
|
it 'has the app as an accessor' do
|
13
13
|
subject.app.should == blank_app
|
14
14
|
end
|
15
15
|
|
16
|
-
it 'is able to access the request' do
|
17
|
-
subject.call({})
|
18
|
-
subject.request.should be_kind_of(Rack::Request)
|
19
|
-
end
|
20
|
-
|
21
16
|
it 'calls through to the app' do
|
22
17
|
subject.call({}).should == [200, {}, 'Hi there.']
|
23
18
|
end
|
@@ -31,7 +26,7 @@ describe Grape::Middleware::Base do
|
|
31
26
|
subject.should_receive(:after)
|
32
27
|
end
|
33
28
|
|
34
|
-
after{ subject.call!({}) }
|
29
|
+
after { subject.call!({}) }
|
35
30
|
end
|
36
31
|
|
37
32
|
it 'is able to access the response' do
|
@@ -41,22 +36,22 @@ describe Grape::Middleware::Base do
|
|
41
36
|
|
42
37
|
context 'options' do
|
43
38
|
it 'persists options passed at initialization' do
|
44
|
-
Grape::Middleware::Base.new(blank_app,
|
39
|
+
Grape::Middleware::Base.new(blank_app, abc: true).options[:abc].should be true
|
45
40
|
end
|
46
41
|
|
47
42
|
context 'defaults' do
|
48
43
|
class ExampleWare < Grape::Middleware::Base
|
49
44
|
def default_options
|
50
|
-
{:
|
45
|
+
{ monkey: true }
|
51
46
|
end
|
52
47
|
end
|
53
48
|
|
54
49
|
it 'persists the default options' do
|
55
|
-
ExampleWare.new(blank_app).options[:monkey].should
|
50
|
+
ExampleWare.new(blank_app).options[:monkey].should be true
|
56
51
|
end
|
57
52
|
|
58
53
|
it 'overrides default options when provided' do
|
59
|
-
ExampleWare.new(blank_app, :
|
54
|
+
ExampleWare.new(blank_app, monkey: false).options[:monkey].should be false
|
60
55
|
end
|
61
56
|
end
|
62
57
|
end
|