grape-security 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +45 -0
- data/.rspec +2 -0
- data/.rubocop.yml +70 -0
- data/.travis.yml +18 -0
- data/.yardopts +2 -0
- data/CHANGELOG.md +314 -0
- data/CONTRIBUTING.md +118 -0
- data/Gemfile +21 -0
- data/Guardfile +14 -0
- data/LICENSE +20 -0
- data/README.md +1777 -0
- data/RELEASING.md +105 -0
- data/Rakefile +69 -0
- data/UPGRADING.md +124 -0
- data/grape-security.gemspec +39 -0
- data/grape.png +0 -0
- data/lib/grape.rb +99 -0
- data/lib/grape/api.rb +646 -0
- data/lib/grape/cookies.rb +39 -0
- data/lib/grape/endpoint.rb +533 -0
- data/lib/grape/error_formatter/base.rb +31 -0
- data/lib/grape/error_formatter/json.rb +15 -0
- data/lib/grape/error_formatter/txt.rb +16 -0
- data/lib/grape/error_formatter/xml.rb +15 -0
- data/lib/grape/exceptions/base.rb +66 -0
- data/lib/grape/exceptions/incompatible_option_values.rb +10 -0
- data/lib/grape/exceptions/invalid_formatter.rb +10 -0
- data/lib/grape/exceptions/invalid_versioner_option.rb +10 -0
- data/lib/grape/exceptions/invalid_with_option_for_represent.rb +10 -0
- data/lib/grape/exceptions/missing_mime_type.rb +10 -0
- data/lib/grape/exceptions/missing_option.rb +10 -0
- data/lib/grape/exceptions/missing_vendor_option.rb +10 -0
- data/lib/grape/exceptions/unknown_options.rb +10 -0
- data/lib/grape/exceptions/unknown_validator.rb +10 -0
- data/lib/grape/exceptions/validation.rb +26 -0
- data/lib/grape/exceptions/validation_errors.rb +43 -0
- data/lib/grape/formatter/base.rb +31 -0
- data/lib/grape/formatter/json.rb +12 -0
- data/lib/grape/formatter/serializable_hash.rb +35 -0
- data/lib/grape/formatter/txt.rb +11 -0
- data/lib/grape/formatter/xml.rb +12 -0
- data/lib/grape/http/request.rb +26 -0
- data/lib/grape/locale/en.yml +32 -0
- data/lib/grape/middleware/auth/base.rb +30 -0
- data/lib/grape/middleware/auth/basic.rb +13 -0
- data/lib/grape/middleware/auth/digest.rb +13 -0
- data/lib/grape/middleware/auth/oauth2.rb +83 -0
- data/lib/grape/middleware/base.rb +62 -0
- data/lib/grape/middleware/error.rb +89 -0
- data/lib/grape/middleware/filter.rb +17 -0
- data/lib/grape/middleware/formatter.rb +150 -0
- data/lib/grape/middleware/globals.rb +13 -0
- data/lib/grape/middleware/versioner.rb +32 -0
- data/lib/grape/middleware/versioner/accept_version_header.rb +67 -0
- data/lib/grape/middleware/versioner/header.rb +132 -0
- data/lib/grape/middleware/versioner/param.rb +42 -0
- data/lib/grape/middleware/versioner/path.rb +52 -0
- data/lib/grape/namespace.rb +23 -0
- data/lib/grape/parser/base.rb +29 -0
- data/lib/grape/parser/json.rb +11 -0
- data/lib/grape/parser/xml.rb +11 -0
- data/lib/grape/path.rb +70 -0
- data/lib/grape/route.rb +27 -0
- data/lib/grape/util/content_types.rb +18 -0
- data/lib/grape/util/deep_merge.rb +23 -0
- data/lib/grape/util/hash_stack.rb +120 -0
- data/lib/grape/validations.rb +322 -0
- data/lib/grape/validations/coerce.rb +63 -0
- data/lib/grape/validations/default.rb +25 -0
- data/lib/grape/validations/exactly_one_of.rb +26 -0
- data/lib/grape/validations/mutual_exclusion.rb +25 -0
- data/lib/grape/validations/presence.rb +16 -0
- data/lib/grape/validations/regexp.rb +12 -0
- data/lib/grape/validations/values.rb +23 -0
- data/lib/grape/version.rb +3 -0
- data/spec/grape/api_spec.rb +2571 -0
- data/spec/grape/endpoint_spec.rb +784 -0
- data/spec/grape/entity_spec.rb +324 -0
- data/spec/grape/exceptions/invalid_formatter_spec.rb +18 -0
- data/spec/grape/exceptions/invalid_versioner_option_spec.rb +18 -0
- data/spec/grape/exceptions/missing_mime_type_spec.rb +18 -0
- data/spec/grape/exceptions/missing_option_spec.rb +18 -0
- data/spec/grape/exceptions/unknown_options_spec.rb +18 -0
- data/spec/grape/exceptions/unknown_validator_spec.rb +18 -0
- data/spec/grape/exceptions/validation_errors_spec.rb +19 -0
- data/spec/grape/middleware/auth/basic_spec.rb +31 -0
- data/spec/grape/middleware/auth/digest_spec.rb +47 -0
- data/spec/grape/middleware/auth/oauth2_spec.rb +135 -0
- data/spec/grape/middleware/base_spec.rb +58 -0
- data/spec/grape/middleware/error_spec.rb +45 -0
- data/spec/grape/middleware/exception_spec.rb +184 -0
- data/spec/grape/middleware/formatter_spec.rb +258 -0
- data/spec/grape/middleware/versioner/accept_version_header_spec.rb +121 -0
- data/spec/grape/middleware/versioner/header_spec.rb +302 -0
- data/spec/grape/middleware/versioner/param_spec.rb +58 -0
- data/spec/grape/middleware/versioner/path_spec.rb +44 -0
- data/spec/grape/middleware/versioner_spec.rb +22 -0
- data/spec/grape/path_spec.rb +229 -0
- data/spec/grape/util/hash_stack_spec.rb +132 -0
- data/spec/grape/validations/coerce_spec.rb +208 -0
- data/spec/grape/validations/default_spec.rb +123 -0
- data/spec/grape/validations/exactly_one_of_spec.rb +71 -0
- data/spec/grape/validations/mutual_exclusion_spec.rb +61 -0
- data/spec/grape/validations/presence_spec.rb +142 -0
- data/spec/grape/validations/regexp_spec.rb +40 -0
- data/spec/grape/validations/values_spec.rb +152 -0
- data/spec/grape/validations/zh-CN.yml +10 -0
- data/spec/grape/validations_spec.rb +994 -0
- data/spec/shared/versioning_examples.rb +121 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/support/basic_auth_encode_helpers.rb +3 -0
- data/spec/support/content_type_helpers.rb +11 -0
- data/spec/support/versioned_helpers.rb +50 -0
- metadata +421 -0
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'base64'
|
4
|
+
|
5
|
+
describe Grape::Middleware::Auth::Basic do
|
6
|
+
def app
|
7
|
+
Rack::Builder.new do |b|
|
8
|
+
b.use Grape::Middleware::Error
|
9
|
+
b.use(Grape::Middleware::Auth::Basic) do |u, p|
|
10
|
+
u && p && u == p
|
11
|
+
end
|
12
|
+
b.run lambda { |env| [200, {}, ["Hello there."]] }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'throws a 401 if no auth is given' do
|
17
|
+
@proc = lambda { false }
|
18
|
+
get '/whatever'
|
19
|
+
expect(last_response.status).to eq(401)
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'authenticates if given valid creds' do
|
23
|
+
get '/whatever', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('admin', 'admin')
|
24
|
+
expect(last_response.status).to eq(200)
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'throws a 401 is wrong auth is given' do
|
28
|
+
get '/whatever', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('admin', 'wrong')
|
29
|
+
expect(last_response.status).to eq(401)
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec::Matchers.define :be_challenge do
|
4
|
+
match do |actual_response|
|
5
|
+
actual_response.status == 401 &&
|
6
|
+
actual_response['WWW-Authenticate'] =~ /^Digest / &&
|
7
|
+
actual_response.body.empty?
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class Test < Grape::API
|
12
|
+
http_digest(realm: 'Test Api', opaque: 'secret') do |username|
|
13
|
+
{ 'foo' => 'bar' }[username]
|
14
|
+
end
|
15
|
+
|
16
|
+
get '/test' do
|
17
|
+
[{ hey: 'you' }, { there: 'bar' }, { foo: 'baz' }]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe Grape::Middleware::Auth::Digest do
|
22
|
+
def app
|
23
|
+
Test
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'is a digest authentication challenge' do
|
27
|
+
get '/test'
|
28
|
+
expect(last_response).to be_challenge
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'throws a 401 if no auth is given' do
|
32
|
+
get '/test'
|
33
|
+
expect(last_response.status).to eq(401)
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'authenticates if given valid creds' do
|
37
|
+
digest_authorize "foo", "bar"
|
38
|
+
get '/test'
|
39
|
+
expect(last_response.status).to eq(200)
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'throws a 401 if given invalid creds' do
|
43
|
+
digest_authorize "bar", "foo"
|
44
|
+
get '/test'
|
45
|
+
expect(last_response.status).to eq(401)
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Grape::Middleware::Auth::OAuth2 do
|
4
|
+
class FakeToken
|
5
|
+
attr_accessor :token
|
6
|
+
|
7
|
+
def self.verify(token)
|
8
|
+
FakeToken.new(token) if !!token && %w(g e).include?(token[0..0])
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(token)
|
12
|
+
@token = token
|
13
|
+
end
|
14
|
+
|
15
|
+
def expired?
|
16
|
+
@token[0..0] == 'e'
|
17
|
+
end
|
18
|
+
|
19
|
+
def permission_for?(env)
|
20
|
+
env['PATH_INFO'] == '/forbidden' ? false : true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def app
|
25
|
+
Rack::Builder.app do
|
26
|
+
use Grape::Middleware::Auth::OAuth2, token_class: 'FakeToken'
|
27
|
+
run lambda { |env| [200, {}, [(env['api.token'].token if env['api.token'])]] }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'with the token in the query string' do
|
32
|
+
context 'and a valid token' do
|
33
|
+
before { get '/awesome?access_token=g123' }
|
34
|
+
|
35
|
+
it 'sets env["api.token"]' do
|
36
|
+
expect(last_response.body).to eq('g123')
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context 'and an invalid token' do
|
41
|
+
before do
|
42
|
+
@err = catch :error do
|
43
|
+
get '/awesome?access_token=b123'
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'throws an error' do
|
48
|
+
expect(@err[:status]).to eq(401)
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'sets the WWW-Authenticate header in the response' do
|
52
|
+
expect(@err[:headers]['WWW-Authenticate']).to eq("OAuth realm='OAuth API', error='invalid_grant'")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context 'with an expired token' do
|
58
|
+
before do
|
59
|
+
@err = catch :error do
|
60
|
+
get '/awesome?access_token=e123'
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'throws an error' do
|
65
|
+
expect(@err[:status]).to eq(401)
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'sets the WWW-Authenticate header in the response to error' do
|
69
|
+
expect(@err[:headers]['WWW-Authenticate']).to eq("OAuth realm='OAuth API', error='invalid_grant'")
|
70
|
+
end
|
71
|
+
end
|
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
|
+
expect(last_response.body).to eq('g123')
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
context 'with the token in the POST body' do
|
86
|
+
before do
|
87
|
+
post '/awesome', 'access_token' => 'g123'
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'sets env["api.token"]' do
|
91
|
+
expect(last_response.body).to eq('g123')
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context 'when accessing something outside its scope' do
|
96
|
+
before do
|
97
|
+
@err = catch :error do
|
98
|
+
get '/forbidden?access_token=g123'
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'throws an error' do
|
103
|
+
expect(@err[:status]).to eq(403)
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'sets the WWW-Authenticate header in the response to error' do
|
107
|
+
expect(@err[:headers]['WWW-Authenticate']).to eq("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
|
+
expect(last_response.status).to eq(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
|
+
expect(last_response.body).to eq('g123')
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Grape::Middleware::Base do
|
4
|
+
subject { Grape::Middleware::Base.new(blank_app) }
|
5
|
+
let(:blank_app) { lambda { |_| [200, {}, 'Hi there.'] } }
|
6
|
+
|
7
|
+
before do
|
8
|
+
# Keep it one object for testing.
|
9
|
+
allow(subject).to receive(:dup).and_return(subject)
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'has the app as an accessor' do
|
13
|
+
expect(subject.app).to eq(blank_app)
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'calls through to the app' do
|
17
|
+
expect(subject.call({})).to eq([200, {}, 'Hi there.'])
|
18
|
+
end
|
19
|
+
|
20
|
+
context 'callbacks' do
|
21
|
+
it 'calls #before' do
|
22
|
+
expect(subject).to receive(:before)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'calls #after' do
|
26
|
+
expect(subject).to receive(:after)
|
27
|
+
end
|
28
|
+
|
29
|
+
after { subject.call!({}) }
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'is able to access the response' do
|
33
|
+
subject.call({})
|
34
|
+
expect(subject.response).to be_kind_of(Rack::Response)
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'options' do
|
38
|
+
it 'persists options passed at initialization' do
|
39
|
+
expect(Grape::Middleware::Base.new(blank_app, abc: true).options[:abc]).to be true
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'defaults' do
|
43
|
+
class ExampleWare < Grape::Middleware::Base
|
44
|
+
def default_options
|
45
|
+
{ monkey: true }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'persists the default options' do
|
50
|
+
expect(ExampleWare.new(blank_app).options[:monkey]).to be true
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'overrides default options when provided' do
|
54
|
+
expect(ExampleWare.new(blank_app, monkey: false).options[:monkey]).to be false
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Grape::Middleware::Error do
|
4
|
+
class ErrApp
|
5
|
+
class << self
|
6
|
+
attr_accessor :error
|
7
|
+
attr_accessor :format
|
8
|
+
|
9
|
+
def call(env)
|
10
|
+
throw :error, error
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def app
|
16
|
+
Rack::Builder.app do
|
17
|
+
use Grape::Middleware::Error, default_message: 'Aww, hamburgers.'
|
18
|
+
run ErrApp
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'sets the status code appropriately' do
|
23
|
+
ErrApp.error = { status: 410 }
|
24
|
+
get '/'
|
25
|
+
expect(last_response.status).to eq(410)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'sets the error message appropriately' do
|
29
|
+
ErrApp.error = { message: 'Awesome stuff.' }
|
30
|
+
get '/'
|
31
|
+
expect(last_response.body).to eq('Awesome stuff.')
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'defaults to a 500 status' do
|
35
|
+
ErrApp.error = {}
|
36
|
+
get '/'
|
37
|
+
expect(last_response.status).to eq(500)
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'has a default message' do
|
41
|
+
ErrApp.error = {}
|
42
|
+
get '/'
|
43
|
+
expect(last_response.body).to eq('Aww, hamburgers.')
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'active_support/core_ext/hash'
|
3
|
+
|
4
|
+
describe Grape::Middleware::Error do
|
5
|
+
|
6
|
+
# raises a text exception
|
7
|
+
class ExceptionApp
|
8
|
+
class << self
|
9
|
+
def call(env)
|
10
|
+
raise "rain!"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# raises a hash error
|
16
|
+
class ErrorHashApp
|
17
|
+
class << self
|
18
|
+
def error!(message, status)
|
19
|
+
throw :error, message: { error: message, detail: "missing widget" }, status: status
|
20
|
+
end
|
21
|
+
|
22
|
+
def call(env)
|
23
|
+
error!("rain!", 401)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# raises an error!
|
29
|
+
class AccessDeniedApp
|
30
|
+
class << self
|
31
|
+
def error!(message, status)
|
32
|
+
throw :error, message: message, status: status
|
33
|
+
end
|
34
|
+
|
35
|
+
def call(env)
|
36
|
+
error!("Access Denied", 401)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# raises a custom error
|
42
|
+
class CustomError < Grape::Exceptions::Base
|
43
|
+
end
|
44
|
+
|
45
|
+
class CustomErrorApp
|
46
|
+
class << self
|
47
|
+
def call(env)
|
48
|
+
raise CustomError, status: 400, message: 'failed validation'
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
attr_reader :app
|
54
|
+
|
55
|
+
it 'does not trap errors by default' do
|
56
|
+
@app ||= Rack::Builder.app do
|
57
|
+
use Grape::Middleware::Error
|
58
|
+
run ExceptionApp
|
59
|
+
end
|
60
|
+
expect { get '/' }.to raise_error
|
61
|
+
end
|
62
|
+
|
63
|
+
context 'with rescue_all set to true' do
|
64
|
+
it 'sets the message appropriately' do
|
65
|
+
@app ||= Rack::Builder.app do
|
66
|
+
use Grape::Middleware::Error, rescue_all: true
|
67
|
+
run ExceptionApp
|
68
|
+
end
|
69
|
+
get '/'
|
70
|
+
expect(last_response.body).to eq("rain!")
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'defaults to a 500 status' do
|
74
|
+
@app ||= Rack::Builder.app do
|
75
|
+
use Grape::Middleware::Error, rescue_all: true
|
76
|
+
run ExceptionApp
|
77
|
+
end
|
78
|
+
get '/'
|
79
|
+
expect(last_response.status).to eq(500)
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'is possible to specify a different default status code' do
|
83
|
+
@app ||= Rack::Builder.app do
|
84
|
+
use Grape::Middleware::Error, rescue_all: true, default_status: 500
|
85
|
+
run ExceptionApp
|
86
|
+
end
|
87
|
+
get '/'
|
88
|
+
expect(last_response.status).to eq(500)
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'is possible to return errors in json format' do
|
92
|
+
@app ||= Rack::Builder.app do
|
93
|
+
use Grape::Middleware::Error, rescue_all: true, format: :json
|
94
|
+
run ExceptionApp
|
95
|
+
end
|
96
|
+
get '/'
|
97
|
+
expect(last_response.body).to eq('{"error":"rain!"}')
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'is possible to return hash errors in json format' do
|
101
|
+
@app ||= Rack::Builder.app do
|
102
|
+
use Grape::Middleware::Error, rescue_all: true, format: :json
|
103
|
+
run ErrorHashApp
|
104
|
+
end
|
105
|
+
get '/'
|
106
|
+
expect(['{"error":"rain!","detail":"missing widget"}',
|
107
|
+
'{"detail":"missing widget","error":"rain!"}']).to include(last_response.body)
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'is possible to return errors in jsonapi format' do
|
111
|
+
@app ||= Rack::Builder.app do
|
112
|
+
use Grape::Middleware::Error, rescue_all: true, format: :jsonapi
|
113
|
+
run ExceptionApp
|
114
|
+
end
|
115
|
+
get '/'
|
116
|
+
expect(last_response.body).to eq('{"error":"rain!"}')
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'is possible to return hash errors in jsonapi format' do
|
120
|
+
@app ||= Rack::Builder.app do
|
121
|
+
use Grape::Middleware::Error, rescue_all: true, format: :jsonapi
|
122
|
+
run ErrorHashApp
|
123
|
+
end
|
124
|
+
get '/'
|
125
|
+
expect(['{"error":"rain!","detail":"missing widget"}',
|
126
|
+
'{"detail":"missing widget","error":"rain!"}']).to include(last_response.body)
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'is possible to return errors in xml format' do
|
130
|
+
@app ||= Rack::Builder.app do
|
131
|
+
use Grape::Middleware::Error, rescue_all: true, format: :xml
|
132
|
+
run ExceptionApp
|
133
|
+
end
|
134
|
+
get '/'
|
135
|
+
expect(last_response.body).to eq("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<error>\n <message>rain!</message>\n</error>\n")
|
136
|
+
end
|
137
|
+
|
138
|
+
it 'is possible to return hash errors in xml format' do
|
139
|
+
@app ||= Rack::Builder.app do
|
140
|
+
use Grape::Middleware::Error, rescue_all: true, format: :xml
|
141
|
+
run ErrorHashApp
|
142
|
+
end
|
143
|
+
get '/'
|
144
|
+
expect(["<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<error>\n <detail>missing widget</detail>\n <error>rain!</error>\n</error>\n",
|
145
|
+
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<error>\n <error>rain!</error>\n <detail>missing widget</detail>\n</error>\n"]).to include(last_response.body)
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'is possible to specify a custom formatter' do
|
149
|
+
@app ||= Rack::Builder.app do
|
150
|
+
use Grape::Middleware::Error, rescue_all: true,
|
151
|
+
format: :custom,
|
152
|
+
error_formatters: {
|
153
|
+
custom: lambda { |message, backtrace, options, env|
|
154
|
+
{ custom_formatter: message }.inspect
|
155
|
+
}
|
156
|
+
}
|
157
|
+
run ExceptionApp
|
158
|
+
end
|
159
|
+
get '/'
|
160
|
+
expect(last_response.body).to eq('{:custom_formatter=>"rain!"}')
|
161
|
+
end
|
162
|
+
|
163
|
+
it 'does not trap regular error! codes' do
|
164
|
+
@app ||= Rack::Builder.app do
|
165
|
+
use Grape::Middleware::Error
|
166
|
+
run AccessDeniedApp
|
167
|
+
end
|
168
|
+
get '/'
|
169
|
+
expect(last_response.status).to eq(401)
|
170
|
+
end
|
171
|
+
|
172
|
+
it 'responds to custom Grape exceptions appropriately' do
|
173
|
+
@app ||= Rack::Builder.app do
|
174
|
+
use Grape::Middleware::Error, rescue_all: false
|
175
|
+
run CustomErrorApp
|
176
|
+
end
|
177
|
+
|
178
|
+
get '/'
|
179
|
+
expect(last_response.status).to eq(400)
|
180
|
+
expect(last_response.body).to eq('failed validation')
|
181
|
+
end
|
182
|
+
|
183
|
+
end
|
184
|
+
end
|