swaggable 0.6.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/swaggable.rb +32 -7
- data/lib/swaggable/api_validator.rb +42 -0
- data/lib/swaggable/check_body_schema.rb +72 -0
- data/lib/swaggable/check_expected_parameters.rb +26 -0
- data/lib/swaggable/check_mandatory_parameters.rb +26 -0
- data/lib/swaggable/check_request_content_type.rb +26 -0
- data/lib/swaggable/check_response_code.rb +24 -0
- data/lib/swaggable/check_response_content_type.rb +26 -0
- data/lib/swaggable/endpoint_definition.rb +30 -2
- data/lib/swaggable/endpoint_validator.rb +27 -0
- data/lib/swaggable/errors.rb +9 -0
- data/lib/swaggable/errors/validations_collection.rb +44 -0
- data/lib/swaggable/grape_adapter.rb +2 -2
- data/lib/swaggable/mime_type_definition.rb +73 -0
- data/lib/swaggable/mime_types_collection.rb +60 -0
- data/lib/swaggable/parameter_definition.rb +19 -0
- data/lib/swaggable/query_params.rb +50 -0
- data/lib/swaggable/rack_request_adapter.rb +88 -0
- data/lib/swaggable/rack_response_adapter.rb +49 -0
- data/lib/swaggable/swagger_2_serializer.rb +3 -3
- data/lib/swaggable/validating_rack_app.rb +30 -0
- data/lib/swaggable/version.rb +1 -1
- data/spec/{swaggable/integration_spec.rb → integration/dsl_spec.rb} +0 -40
- data/spec/integration/rack_app_spec.rb +44 -0
- data/spec/integration/validating_rack_app_spec.rb +50 -0
- data/spec/spec_helper.rb +9 -5
- data/spec/swaggable/api_validator_spec.rb +61 -0
- data/spec/swaggable/check_body_schema_spec.rb +94 -0
- data/spec/swaggable/check_expected_parameters_spec.rb +110 -0
- data/spec/swaggable/check_mandatory_parameters_spec.rb +110 -0
- data/spec/swaggable/check_request_content_type_spec.rb +51 -0
- data/spec/swaggable/check_response_code_spec.rb +37 -0
- data/spec/swaggable/check_response_content_type_spec.rb +51 -0
- data/spec/swaggable/endpoint_definition_spec.rb +47 -0
- data/spec/swaggable/endpoint_validator_spec.rb +102 -0
- data/spec/swaggable/errors/validations_collection_spec.rb +78 -0
- data/spec/swaggable/grape_adapter_spec.rb +2 -2
- data/spec/swaggable/mime_type_definition_spec.rb +96 -0
- data/spec/swaggable/mime_types_collection_spec.rb +83 -0
- data/spec/swaggable/parameter_definition_spec.rb +23 -0
- data/spec/swaggable/query_params_spec.rb +37 -0
- data/spec/swaggable/rack_request_adapter_spec.rb +89 -0
- data/spec/swaggable/rack_response_adapter_spec.rb +46 -0
- data/spec/swaggable/swagger_2_serializer_spec.rb +4 -2
- data/spec/swaggable/swagger_2_validator_spec.rb +2 -2
- data/spec/swaggable/validating_rack_app_spec.rb +91 -0
- data/swaggable.gemspec +1 -0
- metadata +68 -4
@@ -0,0 +1,44 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
require 'rack/test'
|
3
|
+
require 'grape'
|
4
|
+
|
5
|
+
RSpec.describe 'Integration' do
|
6
|
+
context 'RackApp' do
|
7
|
+
include Rack::Test::Methods
|
8
|
+
|
9
|
+
def app
|
10
|
+
subject
|
11
|
+
end
|
12
|
+
|
13
|
+
subject { rack_app }
|
14
|
+
let(:rack_app) { Swaggable::RackApp.new(api_definition: api_def) }
|
15
|
+
let(:api_def) { Swaggable::ApiDefinition.from_grape_api(grape_api) }
|
16
|
+
|
17
|
+
let(:grape_api) do
|
18
|
+
Class.new(Grape::API).tap do |g|
|
19
|
+
g.params do
|
20
|
+
requires :user_uuid
|
21
|
+
end
|
22
|
+
|
23
|
+
g.get '/' do
|
24
|
+
status 200
|
25
|
+
body({some: 'body'})
|
26
|
+
end
|
27
|
+
|
28
|
+
end.tap do |grape|
|
29
|
+
stub_const('MyGrapeApi', grape)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'has status 200 OK' do
|
34
|
+
get '/'
|
35
|
+
expect(last_response.status).to eq 200
|
36
|
+
expect(last_response.headers['Content-Type']).to eq 'application/json'
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'validates' do
|
40
|
+
expect(subject.validate!).to be true
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
require 'rack/test'
|
3
|
+
require 'grape'
|
4
|
+
|
5
|
+
RSpec.describe 'Integration' do
|
6
|
+
describe 'ValidatorRackApp' do
|
7
|
+
include Rack::Test::Methods
|
8
|
+
|
9
|
+
def app_to_validate
|
10
|
+
@app_to_validate ||= -> env { [200, {}, []] }
|
11
|
+
end
|
12
|
+
|
13
|
+
def app
|
14
|
+
Swaggable::ValidatingRackApp.new app: app_to_validate, definition: definition
|
15
|
+
end
|
16
|
+
|
17
|
+
let :definition do
|
18
|
+
Swaggable::ApiDefinition.new do
|
19
|
+
endpoints.add_new do
|
20
|
+
path '/existing_endpoint'
|
21
|
+
verb :get
|
22
|
+
|
23
|
+
responses.add_new do
|
24
|
+
status 200
|
25
|
+
description 'Success'
|
26
|
+
end
|
27
|
+
|
28
|
+
consumes << :json
|
29
|
+
produces << :json
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'does nothing if the validations pass' do
|
35
|
+
expect{ get '/existing_endpoint', {}, 'CONTENT_TYPE' => 'application/json' }.
|
36
|
+
not_to raise_error
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'raises an exception if request validation doesn\'t pass' do
|
40
|
+
expect{ get '/existing_endpoint', {}, 'CONTENT_TYPE' => 'application/xml' }.
|
41
|
+
to raise_error Swaggable::Errors::Validation
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'raises an exception if response validation doesn\'t pass' do
|
45
|
+
@app_to_validate = -> env { [418, {'Content-Type' => 'application/xml'}, ['']] }
|
46
|
+
expect{ get '/existing_endpoint' }.to raise_error Swaggable::Errors::Validation
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
data/spec/spec_helper.rb
CHANGED
@@ -4,11 +4,15 @@ require 'pry'
|
|
4
4
|
require 'webmock/rspec'
|
5
5
|
|
6
6
|
RSpec.configure do |config|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
7
|
+
config.color = true
|
8
|
+
config.tty = true
|
9
|
+
config.disable_monkey_patching!
|
10
|
+
# config.full_backtrace = true
|
11
|
+
# config.formatter = :documentation # :documentation, :progress, :html, :textmate
|
12
|
+
|
13
|
+
config.mock_with :rspec do |mocks|
|
14
|
+
mocks.verify_partial_doubles = true
|
15
|
+
end
|
12
16
|
end
|
13
17
|
|
14
18
|
WebMock.disable_net_connect!(:allow => "codeclimate.com")
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
require 'rack'
|
3
|
+
|
4
|
+
RSpec.describe 'Swaggable::ApiValidator' do
|
5
|
+
subject { subject_instance }
|
6
|
+
let(:subject_instance) { subject_class.new definition: api_definition, request: api_request }
|
7
|
+
let(:subject_class) { Swaggable::ApiValidator }
|
8
|
+
let(:api_request) { request :get, '/existing_endpoint' }
|
9
|
+
let(:api_response) { [200, {}, []] }
|
10
|
+
|
11
|
+
let :api_definition do
|
12
|
+
Swaggable::ApiDefinition.new do
|
13
|
+
endpoints.add_new do
|
14
|
+
path '/existing_endpoint'
|
15
|
+
verb :get
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def request verb, path
|
21
|
+
Rack::MockRequest.env_for path, 'REQUEST_METHOD' => verb
|
22
|
+
end
|
23
|
+
|
24
|
+
describe '#endpoint_validator' do
|
25
|
+
it 'has the right endpoint' do
|
26
|
+
expect(subject.endpoint_validator.endpoint).to be subject.endpoint
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'raises an exception if no endpoint was found' do
|
30
|
+
api_definition.endpoints.first.path '/another_path'
|
31
|
+
expect{ subject.endpoint_validator }.
|
32
|
+
to raise_error(Swaggable::Errors::EndpointNotFound)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe '#errors_for_request()' do
|
37
|
+
it 'delegates to the endpoint_validator' do
|
38
|
+
errors = double('errors')
|
39
|
+
|
40
|
+
allow(subject.endpoint_validator).
|
41
|
+
to receive(:errors_for_request).
|
42
|
+
with(api_request).
|
43
|
+
and_return(errors)
|
44
|
+
|
45
|
+
expect(subject.errors_for_request).to be errors
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe '#errors_for_response(response)' do
|
50
|
+
it 'delegates to the endpoint_validator' do
|
51
|
+
errors = double('errors')
|
52
|
+
|
53
|
+
allow(subject.endpoint_validator).
|
54
|
+
to receive(:errors_for_response).
|
55
|
+
with(api_response).
|
56
|
+
and_return(errors)
|
57
|
+
|
58
|
+
expect(subject.errors_for_response api_response).to be errors
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
require 'rack'
|
3
|
+
|
4
|
+
RSpec.describe 'Swaggable::CheckBodySchema' do
|
5
|
+
subject { subject_instance }
|
6
|
+
let(:subject_instance) { subject_class.new }
|
7
|
+
let(:subject_class) { Swaggable::CheckBodySchema }
|
8
|
+
|
9
|
+
|
10
|
+
let(:request) do
|
11
|
+
Swaggable::RackRequestAdapter.new Rack::MockRequest.env_for('/', {
|
12
|
+
'REQUEST_METHOD' => 'POST',
|
13
|
+
'CONTENT_TYPE' => content_type,
|
14
|
+
:input => body,
|
15
|
+
})
|
16
|
+
end
|
17
|
+
|
18
|
+
let(:verb) { 'POST' }
|
19
|
+
let(:content_type) { nil }
|
20
|
+
let(:body) { nil }
|
21
|
+
|
22
|
+
let(:endpoint) { Swaggable::EndpointDefinition.new }
|
23
|
+
let(:body_definition) { endpoint.parameters.add_new { location :body } }
|
24
|
+
|
25
|
+
def do_run
|
26
|
+
subject_class.call endpoint: endpoint, request: request
|
27
|
+
end
|
28
|
+
|
29
|
+
describe '.call' do
|
30
|
+
context 'present and there is no schema' do
|
31
|
+
let(:body) { '{}' }
|
32
|
+
before { body_definition.required true }
|
33
|
+
|
34
|
+
it 'returns no error' do
|
35
|
+
expect(do_run).to be_empty
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context 'present and matches schema' do
|
40
|
+
let(:content_type) { 'application/json' }
|
41
|
+
let(:body) { '{"name":"John"}' }
|
42
|
+
|
43
|
+
before do
|
44
|
+
body_definition.schema.attributes.add_new { name 'name'; type :string }
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'returns no error' do
|
48
|
+
expect(do_run).to be_empty
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'when required but not present' do
|
53
|
+
before { body_definition.required true }
|
54
|
+
|
55
|
+
it 'returns error' do
|
56
|
+
errors = do_run
|
57
|
+
expect(errors.count).to eq 1
|
58
|
+
expect(errors.first.message).to match /body/
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context 'when present but missing required attribute' do
|
63
|
+
let(:content_type) { 'application/json' }
|
64
|
+
let(:body) { '{}' }
|
65
|
+
|
66
|
+
before do
|
67
|
+
body_definition.required true
|
68
|
+
body_definition.schema.attributes.add_new { name 'name'; type :string; required true }
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'returns error' do
|
72
|
+
errors = do_run
|
73
|
+
expect(errors.count).to eq 1
|
74
|
+
expect(errors.first.message).to match /body/
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
context 'when present but missing unexpected attribute' do
|
79
|
+
let(:content_type) { 'application/json' }
|
80
|
+
let(:body) { '{"made_up_attribute":42}' }
|
81
|
+
|
82
|
+
before do
|
83
|
+
body_definition.required true
|
84
|
+
body_definition.schema.attributes.add_new { name 'name'; type :string }
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'returns error' do
|
88
|
+
errors = do_run
|
89
|
+
expect(errors.count).to eq 1
|
90
|
+
expect(errors.first.message).to match /body/
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
require 'rack'
|
3
|
+
|
4
|
+
RSpec.describe 'Swaggable::CheckExpectedParameters' do
|
5
|
+
subject { subject_instance }
|
6
|
+
let(:subject_instance) { subject_class.new }
|
7
|
+
let(:subject_class) { Swaggable::CheckExpectedParameters }
|
8
|
+
|
9
|
+
let(:parameters) { endpoint.parameters }
|
10
|
+
let(:endpoint) { Swaggable::EndpointDefinition.new { path '/' } }
|
11
|
+
let(:request) { Swaggable::RackRequestAdapter.new Rack::MockRequest.env_for("#{path}?#{query_string}") }
|
12
|
+
let(:path) { '/' }
|
13
|
+
let(:query_string) { '' }
|
14
|
+
|
15
|
+
def do_run
|
16
|
+
subject_class.call endpoint: endpoint, request: request
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '.call' do
|
20
|
+
context 'present parameter is expected' do
|
21
|
+
it 'returns an empty errors list' do
|
22
|
+
expect(do_run).to be_empty
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'present parameter is unexpected' do
|
27
|
+
let(:query_string) { 'name=John' }
|
28
|
+
|
29
|
+
it 'returns an empty errors list' do
|
30
|
+
errors = do_run
|
31
|
+
expect(errors.count).to eq 1
|
32
|
+
expect(errors.first.message).to match /name/
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# context 'all mandatory parameters are present' do
|
37
|
+
# before do
|
38
|
+
# parameters.add_new { name :email; required true }
|
39
|
+
# request.query_parameters[:email] = 'user@example.com'
|
40
|
+
# end
|
41
|
+
|
42
|
+
# it 'returns no errors' do
|
43
|
+
# expect(do_run).to be_empty
|
44
|
+
# end
|
45
|
+
# end
|
46
|
+
|
47
|
+
# context 'missing mandatory parameters undefined location' do
|
48
|
+
# before do
|
49
|
+
# parameters.add_new { name :email; required true }
|
50
|
+
# end
|
51
|
+
|
52
|
+
# it 'returns an error per missing parameter' do
|
53
|
+
# errors = do_run
|
54
|
+
# expect(errors.count).to eq 1
|
55
|
+
# expect(errors.first.message).to match /email/
|
56
|
+
# end
|
57
|
+
# end
|
58
|
+
|
59
|
+
# context 'missing mandatory query param' do
|
60
|
+
# before do
|
61
|
+
# parameters.add_new { name :email; required true; location :query }
|
62
|
+
# end
|
63
|
+
|
64
|
+
# it 'returns an error per missing parameter' do
|
65
|
+
# errors = do_run
|
66
|
+
# expect(errors.count).to eq 1
|
67
|
+
# expect(errors.first.message).to match /email/
|
68
|
+
# end
|
69
|
+
# end
|
70
|
+
|
71
|
+
# context 'missing mandatory path param' do
|
72
|
+
# before do
|
73
|
+
# parameters.add_new { name 'user_id'; required true; location :path }
|
74
|
+
# end
|
75
|
+
|
76
|
+
# it 'returns an error per missing parameter' do
|
77
|
+
# errors = do_run
|
78
|
+
# expect(errors.count).to eq 1
|
79
|
+
# expect(errors.first.message).to match /user_id/
|
80
|
+
# end
|
81
|
+
# end
|
82
|
+
|
83
|
+
# context 'present mandatory path param' do
|
84
|
+
# let(:path) { '/users/37.json' }
|
85
|
+
|
86
|
+
# before do
|
87
|
+
# endpoint.path '/users/{user_id}.json'
|
88
|
+
# parameters.add_new { name 'user_id'; required true; location :path }
|
89
|
+
# end
|
90
|
+
|
91
|
+
# it 'no errors' do
|
92
|
+
# expect(do_run).to be_empty
|
93
|
+
# end
|
94
|
+
# end
|
95
|
+
|
96
|
+
# context 'missing non-mandatory parameters' do
|
97
|
+
# before do
|
98
|
+
# parameters.add_new { name :email; required false }
|
99
|
+
# end
|
100
|
+
|
101
|
+
# it 'returns no errors' do
|
102
|
+
# expect(do_run).to be_empty
|
103
|
+
# end
|
104
|
+
# end
|
105
|
+
|
106
|
+
# # TODO
|
107
|
+
# describe 'header parameter'
|
108
|
+
# describe 'form parameter'
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
require 'rack'
|
3
|
+
|
4
|
+
RSpec.describe 'Swaggable::CheckMandatoryParameters' do
|
5
|
+
subject { subject_instance }
|
6
|
+
let(:subject_instance) { subject_class.new }
|
7
|
+
let(:subject_class) { Swaggable::CheckMandatoryParameters }
|
8
|
+
|
9
|
+
let(:parameters) { endpoint.parameters }
|
10
|
+
let(:endpoint) { Swaggable::EndpointDefinition.new { path '/' } }
|
11
|
+
|
12
|
+
let(:request) do
|
13
|
+
Swaggable::RackRequestAdapter.new Rack::MockRequest.env_for(path, {
|
14
|
+
'REQUEST_METHOD' => verb,
|
15
|
+
'CONTENT_TYPE' => content_type,
|
16
|
+
:input => body,
|
17
|
+
})
|
18
|
+
end
|
19
|
+
|
20
|
+
let(:path) { '/' }
|
21
|
+
let(:verb) { 'GET' }
|
22
|
+
let(:content_type) { nil }
|
23
|
+
let(:body) { nil }
|
24
|
+
|
25
|
+
def do_run
|
26
|
+
subject_class.call endpoint: endpoint, request: request
|
27
|
+
end
|
28
|
+
|
29
|
+
describe '.call' do
|
30
|
+
context 'no mandatory parameters' do
|
31
|
+
it 'returns an empty errors list' do
|
32
|
+
expect(do_run).to be_empty
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'all mandatory parameters are present' do
|
37
|
+
before do
|
38
|
+
parameters.add_new { name :email; required true; location :query }
|
39
|
+
request.query_parameters[:email] = 'user@example.com'
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'returns no errors' do
|
43
|
+
expect(do_run).to be_empty
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context 'missing mandatory parameters undefined location' do
|
48
|
+
before do
|
49
|
+
parameters.add_new { name :email; required true }
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'returns an error per missing parameter' do
|
53
|
+
errors = do_run
|
54
|
+
expect(errors.count).to eq 1
|
55
|
+
expect(errors.first.message).to match /email/
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context 'missing mandatory query param' do
|
60
|
+
before do
|
61
|
+
parameters.add_new { name :email; required true; location :query }
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'returns an error per missing parameter' do
|
65
|
+
errors = do_run
|
66
|
+
expect(errors.count).to eq 1
|
67
|
+
expect(errors.first.message).to match /email/
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context 'missing mandatory path param' do
|
72
|
+
before do
|
73
|
+
parameters.add_new { name 'user_id'; required true; location :path }
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'returns an error per missing parameter' do
|
77
|
+
errors = do_run
|
78
|
+
expect(errors.count).to eq 1
|
79
|
+
expect(errors.first.message).to match /user_id/
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
context 'present mandatory path param' do
|
84
|
+
let(:path) { '/users/37.json' }
|
85
|
+
|
86
|
+
before do
|
87
|
+
endpoint.path '/users/{user_id}.json'
|
88
|
+
parameters.add_new { name 'user_id'; required true; location :path }
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'no errors' do
|
92
|
+
expect(do_run).to be_empty
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
context 'missing non-mandatory parameters' do
|
97
|
+
before do
|
98
|
+
parameters.add_new { name :email; required false }
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'returns no errors' do
|
102
|
+
expect(do_run).to be_empty
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# TODO
|
107
|
+
describe 'header parameter'
|
108
|
+
describe 'form parameter'
|
109
|
+
end
|
110
|
+
end
|