rswag-specs 1.4.0 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/rswag/specs/example_group_helpers.rb +3 -1
- data/lib/rswag/specs/example_helpers.rb +12 -37
- data/lib/rswag/specs/request_factory.rb +113 -66
- data/lib/rswag/specs/response_validator.rb +23 -27
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e08ea4a4b9b1820ae1d7b4729aa995f4118aa0f4
|
4
|
+
data.tar.gz: a02faf75c1c344426b3856ce52d29588251e1b6c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 33f8487725a40410ce5f6c67d9f64511003a12f81b0dd77b23239b935ab215e71b6e2a71d629d6b970c685aaa7ea261dced8c40c6da503ad62c80702bac9c1eb
|
7
|
+
data.tar.gz: be921ad7df6e5c40fe33d1abe9b27a4df61ebb276da2ec4b385b7aae2127334a812b9de0226f92e6ba18d582a73c07ecec2b8a0df3ffef74b15d327ab76ba135
|
@@ -77,7 +77,8 @@ module Rswag
|
|
77
77
|
end
|
78
78
|
|
79
79
|
it "returns a #{metadata[:response][:code]} response" do
|
80
|
-
assert_response_matches_metadata(
|
80
|
+
assert_response_matches_metadata(metadata)
|
81
|
+
block.call(response) if block_given?
|
81
82
|
end
|
82
83
|
else
|
83
84
|
before do |example|
|
@@ -86,6 +87,7 @@ module Rswag
|
|
86
87
|
|
87
88
|
it "returns a #{metadata[:response][:code]} response" do |example|
|
88
89
|
assert_response_matches_metadata(example.metadata, &block)
|
90
|
+
example.instance_exec(response, &block) if block_given?
|
89
91
|
end
|
90
92
|
end
|
91
93
|
end
|
@@ -5,55 +5,30 @@ module Rswag
|
|
5
5
|
module Specs
|
6
6
|
module ExampleHelpers
|
7
7
|
|
8
|
-
def submit_request(
|
9
|
-
|
10
|
-
factory = RequestFactory.new(api_metadata, global_metadata)
|
8
|
+
def submit_request(metadata)
|
9
|
+
request = RequestFactory.new.build_request(metadata, self)
|
11
10
|
|
12
11
|
if RAILS_VERSION < 5
|
13
12
|
send(
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
13
|
+
request[:verb],
|
14
|
+
request[:path],
|
15
|
+
request[:payload],
|
16
|
+
request[:headers]
|
18
17
|
)
|
19
18
|
else
|
20
19
|
send(
|
21
|
-
|
22
|
-
|
20
|
+
request[:verb],
|
21
|
+
request[:path],
|
23
22
|
{
|
24
|
-
params:
|
25
|
-
headers:
|
23
|
+
params: request[:payload],
|
24
|
+
headers: request[:headers]
|
26
25
|
}
|
27
26
|
)
|
28
27
|
end
|
29
28
|
end
|
30
29
|
|
31
|
-
def assert_response_matches_metadata(
|
32
|
-
|
33
|
-
validator = ResponseValidator.new(api_metadata, global_metadata)
|
34
|
-
validator.validate!(response, &block)
|
35
|
-
end
|
36
|
-
|
37
|
-
private
|
38
|
-
|
39
|
-
def rackify_headers(headers)
|
40
|
-
name_value_pairs = headers.map do |name, value|
|
41
|
-
[
|
42
|
-
case name
|
43
|
-
when 'Accept' then 'HTTP_ACCEPT'
|
44
|
-
when 'Content-Type' then 'CONTENT_TYPE'
|
45
|
-
when 'Authorization' then 'HTTP_AUTHORIZATION'
|
46
|
-
else name
|
47
|
-
end,
|
48
|
-
value
|
49
|
-
]
|
50
|
-
end
|
51
|
-
|
52
|
-
Hash[ name_value_pairs ]
|
53
|
-
end
|
54
|
-
|
55
|
-
def rswag_config
|
56
|
-
::Rswag::Specs.config
|
30
|
+
def assert_response_matches_metadata(metadata)
|
31
|
+
ResponseValidator.new.validate!(metadata, response)
|
57
32
|
end
|
58
33
|
end
|
59
34
|
end
|
@@ -1,98 +1,83 @@
|
|
1
1
|
require 'active_support/core_ext/hash/slice'
|
2
|
+
require 'active_support/core_ext/hash/conversions'
|
2
3
|
require 'json'
|
3
4
|
|
4
5
|
module Rswag
|
5
6
|
module Specs
|
6
7
|
class RequestFactory
|
7
8
|
|
8
|
-
def initialize(
|
9
|
-
@
|
10
|
-
@global_metadata = global_metadata
|
9
|
+
def initialize(config = ::Rswag::Specs.config)
|
10
|
+
@config = config
|
11
11
|
end
|
12
12
|
|
13
|
-
def
|
14
|
-
@
|
15
|
-
|
16
|
-
parameters_in(:path).each { |p| t.gsub!("{#{p[:name]}}", example.send(p[:name]).to_s) }
|
17
|
-
t.concat(build_query_string(example))
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
def build_query_string(example)
|
22
|
-
query_string = parameters_in(:query)
|
23
|
-
.map { |p| build_query_string_part(p, example.send(p[:name])) }
|
24
|
-
.join('&')
|
25
|
-
|
26
|
-
query_string.empty? ? '' : "?#{query_string}"
|
27
|
-
end
|
28
|
-
|
29
|
-
def build_body(example)
|
30
|
-
body_parameter = parameters_in(:body).first
|
31
|
-
body_parameter.nil? ? '' : example.send(body_parameter[:name]).to_json
|
32
|
-
end
|
13
|
+
def build_request(metadata, example)
|
14
|
+
swagger_doc = @config.get_swagger_doc(metadata[:swagger_doc])
|
15
|
+
parameters = expand_parameters(metadata, swagger_doc, example)
|
33
16
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
]
|
17
|
+
{}.tap do |request|
|
18
|
+
add_verb(request, metadata)
|
19
|
+
add_path(request, metadata, swagger_doc, parameters, example)
|
20
|
+
add_headers(request, metadata, swagger_doc, parameters, example)
|
21
|
+
add_payload(request, parameters, example)
|
40
22
|
end
|
41
|
-
|
42
|
-
# Add MIME type headers based on produces/consumes metadata
|
43
|
-
produces = @api_metadata[:operation][:produces] || @global_metadata[:produces]
|
44
|
-
consumes = @api_metadata[:operation][:consumes] || @global_metadata[:consumes]
|
45
|
-
name_value_pairs << [ 'Accept', produces.join(';') ] unless produces.nil?
|
46
|
-
name_value_pairs << [ 'Content-Type', consumes.join(';') ] unless consumes.nil?
|
47
|
-
|
48
|
-
Hash[ name_value_pairs ]
|
49
23
|
end
|
50
24
|
|
51
25
|
private
|
52
26
|
|
53
|
-
def
|
54
|
-
|
55
|
-
|
56
|
-
|
27
|
+
def expand_parameters(metadata, swagger_doc, example)
|
28
|
+
operation_params = metadata[:operation][:parameters] || []
|
29
|
+
path_item_params = metadata[:path_item][:parameters] || []
|
30
|
+
security_params = derive_security_params(metadata, swagger_doc)
|
31
|
+
|
32
|
+
operation_params
|
57
33
|
.concat(path_item_params)
|
58
|
-
.
|
34
|
+
.concat(security_params)
|
35
|
+
.map { |p| p['$ref'] ? resolve_parameter(p['$ref'], swagger_doc) : p }
|
36
|
+
.uniq { |p| p[:name] }
|
37
|
+
.reject { |p| p[:required] == false && !example.respond_to?(p[:name]) }
|
38
|
+
end
|
39
|
+
|
40
|
+
def derive_security_params(metadata, swagger_doc)
|
41
|
+
requirements = metadata[:operation][:security] || swagger_doc[:security]
|
42
|
+
scheme_names = requirements ? requirements.map { |r| r.keys.first } : []
|
43
|
+
applicable_schemes = (swagger_doc[:securityDefinitions] || {}).slice(*scheme_names).values
|
59
44
|
|
60
|
-
|
61
|
-
|
62
|
-
.
|
63
|
-
|
45
|
+
applicable_schemes.map do |scheme|
|
46
|
+
param = (scheme[:type] == :apiKey) ? scheme.slice(:name, :in) : { name: 'Authorization', in: :header }
|
47
|
+
param.merge(type: :string)
|
48
|
+
end
|
64
49
|
end
|
65
50
|
|
66
|
-
def resolve_parameter(ref)
|
67
|
-
|
51
|
+
def resolve_parameter(ref, swagger_doc)
|
52
|
+
definitions = swagger_doc[:parameters]
|
68
53
|
key = ref.sub('#/parameters/', '')
|
69
|
-
raise "Referenced parameter '#{ref}' must be defined" unless
|
70
|
-
|
54
|
+
raise "Referenced parameter '#{ref}' must be defined" unless definitions && definitions[key]
|
55
|
+
definitions[key]
|
71
56
|
end
|
72
57
|
|
73
|
-
def
|
74
|
-
|
75
|
-
if scheme[:type] == :apiKey
|
76
|
-
{ name: scheme[:name], type: :string, in: scheme[:in] }
|
77
|
-
else
|
78
|
-
{ name: 'Authorization', type: :string, in: :header } # use auth header for basic & oauth2
|
79
|
-
end
|
80
|
-
end
|
58
|
+
def add_verb(request, metadata)
|
59
|
+
request[:verb] = metadata[:operation][:verb]
|
81
60
|
end
|
82
61
|
|
83
|
-
def
|
84
|
-
|
85
|
-
|
86
|
-
|
62
|
+
def add_path(request, metadata, swagger_doc, parameters, example)
|
63
|
+
template = (swagger_doc[:basePath] || '') + metadata[:path_item][:template]
|
64
|
+
|
65
|
+
request[:path] = template.tap do |template|
|
66
|
+
parameters.select { |p| p[:in] == :path }.each do |p|
|
67
|
+
template.gsub!("{#{p[:name]}}", example.send(p[:name]).to_s)
|
68
|
+
end
|
87
69
|
|
88
|
-
|
89
|
-
|
70
|
+
parameters.select { |p| p[:in] == :query }.each_with_index do |p, i|
|
71
|
+
template.concat(i == 0 ? '?' : '&')
|
72
|
+
template.concat(build_query_string_part(p, example.send(p[:name])))
|
73
|
+
end
|
74
|
+
end
|
90
75
|
end
|
91
76
|
|
92
77
|
def build_query_string_part(param, value)
|
93
|
-
return "#{param[:name]}=#{value.to_s}" unless param[:type].to_sym == :array
|
94
|
-
|
95
78
|
name = param[:name]
|
79
|
+
return "#{name}=#{value.to_s}" unless param[:type].to_sym == :array
|
80
|
+
|
96
81
|
case param[:collectionFormat]
|
97
82
|
when :ssv
|
98
83
|
"#{name}=#{value.join(' ')}"
|
@@ -106,6 +91,68 @@ module Rswag
|
|
106
91
|
"#{name}=#{value.join(',')}" # csv is default
|
107
92
|
end
|
108
93
|
end
|
94
|
+
|
95
|
+
def add_headers(request, metadata, swagger_doc, parameters, example)
|
96
|
+
tuples = parameters
|
97
|
+
.select { |p| p[:in] == :header }
|
98
|
+
.map { |p| [ p[:name], example.send(p[:name]).to_s ] }
|
99
|
+
|
100
|
+
# Accept header
|
101
|
+
produces = metadata[:operation][:produces] || swagger_doc[:produces]
|
102
|
+
if produces
|
103
|
+
accept = example.respond_to?(:'Accept') ? example.send(:'Accept') : produces.first
|
104
|
+
tuples << [ 'Accept', accept ]
|
105
|
+
end
|
106
|
+
|
107
|
+
# Content-Type header
|
108
|
+
consumes = metadata[:operation][:consumes] || swagger_doc[:consumes]
|
109
|
+
if consumes
|
110
|
+
content_type = example.respond_to?(:'Content-Type') ? example.send(:'Content-Type') : consumes.first
|
111
|
+
tuples << [ 'Content-Type', content_type ]
|
112
|
+
end
|
113
|
+
|
114
|
+
# Rails test infrastructure requires rackified headers
|
115
|
+
rackified_tuples = tuples.map do |pair|
|
116
|
+
[
|
117
|
+
case pair[0]
|
118
|
+
when 'Accept' then 'HTTP_ACCEPT'
|
119
|
+
when 'Content-Type' then 'CONTENT_TYPE'
|
120
|
+
when 'Authorization' then 'HTTP_AUTHORIZATION'
|
121
|
+
else pair[0]
|
122
|
+
end,
|
123
|
+
pair[1]
|
124
|
+
]
|
125
|
+
end
|
126
|
+
|
127
|
+
request[:headers] = Hash[ rackified_tuples ]
|
128
|
+
end
|
129
|
+
|
130
|
+
def add_payload(request, parameters, example)
|
131
|
+
content_type = request[:headers]['CONTENT_TYPE']
|
132
|
+
return if content_type.nil?
|
133
|
+
|
134
|
+
if [ 'application/x-www-form-urlencoded', 'multipart/form-data' ].include?(content_type)
|
135
|
+
request[:payload] = build_form_payload(parameters, example)
|
136
|
+
else
|
137
|
+
request[:payload] = build_json_payload(parameters, example)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def build_form_payload(parameters, example)
|
142
|
+
# See http://seejohncode.com/2012/04/29/quick-tip-testing-multipart-uploads-with-rspec/
|
143
|
+
# Rather that serializing with the appropriate encoding (e.g. multipart/form-data),
|
144
|
+
# Rails test infrastructure allows us to send the values directly as a hash
|
145
|
+
# PROS: simple to implement, CONS: serialization/deserialization is bypassed in test
|
146
|
+
tuples = parameters
|
147
|
+
.select { |p| p[:in] == :formData }
|
148
|
+
.map { |p| [ p[:name], example.send(p[:name]) ] }
|
149
|
+
Hash[ tuples ]
|
150
|
+
end
|
151
|
+
|
152
|
+
def build_json_payload(parameters, example)
|
153
|
+
body_param = parameters.select { |p| p[:in] == :body }.first
|
154
|
+
body_param ? example.send(body_param[:name]).to_json : nil
|
155
|
+
end
|
109
156
|
end
|
110
157
|
end
|
111
158
|
end
|
@@ -7,47 +7,43 @@ module Rswag
|
|
7
7
|
module Specs
|
8
8
|
class ResponseValidator
|
9
9
|
|
10
|
-
def initialize(
|
11
|
-
@
|
12
|
-
@global_metadata = global_metadata
|
10
|
+
def initialize(config = ::Rswag::Specs.config)
|
11
|
+
@config = config
|
13
12
|
end
|
14
13
|
|
15
|
-
def validate!(
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
14
|
+
def validate!(metadata, response)
|
15
|
+
swagger_doc = @config.get_swagger_doc(metadata[:swagger_doc])
|
16
|
+
|
17
|
+
validate_code!(metadata, response.code)
|
18
|
+
validate_headers!(metadata, response.headers)
|
19
|
+
validate_body!(metadata, swagger_doc, response.body)
|
20
20
|
end
|
21
21
|
|
22
22
|
private
|
23
23
|
|
24
|
-
def validate_code!(code)
|
25
|
-
|
26
|
-
|
24
|
+
def validate_code!(metadata, code)
|
25
|
+
expected = metadata[:response][:code].to_s
|
26
|
+
if code != expected
|
27
|
+
raise UnexpectedResponse, "Expected response code '#{code}' to match '#{expected}'"
|
27
28
|
end
|
28
29
|
end
|
29
30
|
|
30
|
-
def validate_headers!(headers)
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
header_schema.keys.each do |header_name|
|
35
|
-
raise UnexpectedResponse, "Expected response header #{header_name} to be present" if headers[header_name.to_s].nil?
|
31
|
+
def validate_headers!(metadata, headers)
|
32
|
+
expected = (metadata[:response][:headers] || {}).keys
|
33
|
+
expected.each do |name|
|
34
|
+
raise UnexpectedResponse, "Expected response header #{name} to be present" if headers[name.to_s].nil?
|
36
35
|
end
|
37
36
|
end
|
38
37
|
|
39
|
-
def validate_body!(body)
|
40
|
-
response_schema =
|
38
|
+
def validate_body!(metadata, swagger_doc, body)
|
39
|
+
response_schema = metadata[:response][:schema]
|
41
40
|
return if response_schema.nil?
|
42
41
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
rescue JSON::Schema::ValidationError => ex
|
49
|
-
raise UnexpectedResponse, "Expected response body to match schema: #{ex.message}"
|
50
|
-
end
|
42
|
+
validation_schema = response_schema
|
43
|
+
.merge('$schema' => 'http://tempuri.org/rswag/specs/extended_schema')
|
44
|
+
.merge(swagger_doc.slice(:definitions))
|
45
|
+
errors = JSON::Validator.fully_validate(validation_schema, body)
|
46
|
+
raise UnexpectedResponse, "Expected response body to match schema: #{errors[0]}" if errors.any?
|
51
47
|
end
|
52
48
|
end
|
53
49
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rswag-specs
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Richie Morris
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-07-
|
11
|
+
date: 2017-07-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|