rswag-specs 1.4.0 → 1.5.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/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
|