swaggable 0.6.0 → 0.7.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.
- 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
|