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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: aefe22cf5f08fa506055d2cc653241014d59b968
4
- data.tar.gz: c8d0d80117885539b4fe8361c5a26b34cb90d5e2
3
+ metadata.gz: e08ea4a4b9b1820ae1d7b4729aa995f4118aa0f4
4
+ data.tar.gz: a02faf75c1c344426b3856ce52d29588251e1b6c
5
5
  SHA512:
6
- metadata.gz: 10cf3f7a2899a2b01cf34f183e36edcd6340f498d23a90711d5ff8259adc9ae0201678007fe01e5399b078091c1c202202f978c1382006e5073912c0e46124f1
7
- data.tar.gz: 5063174caf14bc653bfaea6b5feb4ed97c0676a42f472942b573045bf840e96639aa51ff42fe406a17b323d0f093623af68f3713043addba794b344ad3b493bf
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(example.metadata, &block)
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(api_metadata)
9
- global_metadata = rswag_config.get_swagger_doc(api_metadata[:swagger_doc])
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
- api_metadata[:operation][:verb],
15
- factory.build_fullpath(self),
16
- factory.build_body(self),
17
- rackify_headers(factory.build_headers(self)) # Rails test infrastructure requires Rack headers
13
+ request[:verb],
14
+ request[:path],
15
+ request[:payload],
16
+ request[:headers]
18
17
  )
19
18
  else
20
19
  send(
21
- api_metadata[:operation][:verb],
22
- factory.build_fullpath(self),
20
+ request[:verb],
21
+ request[:path],
23
22
  {
24
- params: factory.build_body(self),
25
- headers: factory.build_headers(self)
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(api_metadata, &block)
32
- global_metadata = rswag_config.get_swagger_doc(api_metadata[:swagger_doc])
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(api_metadata, global_metadata)
9
- @api_metadata = api_metadata
10
- @global_metadata = global_metadata
9
+ def initialize(config = ::Rswag::Specs.config)
10
+ @config = config
11
11
  end
12
12
 
13
- def build_fullpath(example)
14
- @api_metadata[:path_item][:template].dup.tap do |t|
15
- t.prepend(@global_metadata[:basePath] || '')
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
- def build_headers(example)
35
- name_value_pairs = parameters_in(:header).map do |param|
36
- [
37
- param[:name],
38
- example.send(param[:name]).to_s
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 parameters_in(location)
54
- path_item_params = @api_metadata[:path_item][:parameters] || []
55
- operation_params = @api_metadata[:operation][:parameters] || []
56
- applicable_params = operation_params
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
- .uniq { |p| p[:name] } # operation params should override path_item params
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
- applicable_params
61
- .map { |p| p['$ref'] ? resolve_parameter(p['$ref']) : p } # resolve any references
62
- .concat(security_parameters)
63
- .select { |p| p[:in] == location }
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
- defined_params = @global_metadata[:parameters]
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 defined_params && defined_params[key]
70
- defined_params[key]
54
+ raise "Referenced parameter '#{ref}' must be defined" unless definitions && definitions[key]
55
+ definitions[key]
71
56
  end
72
57
 
73
- def security_parameters
74
- applicable_security_schemes.map do |scheme|
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 applicable_security_schemes
84
- # First figure out the security requirement applicable to the operation
85
- requirements = @api_metadata[:operation][:security] || @global_metadata[:security]
86
- scheme_names = requirements ? requirements.map { |r| r.keys.first } : []
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
- # Then obtain the scheme definitions for those requirements
89
- (@global_metadata[:securityDefinitions] || {}).slice(*scheme_names).values
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(api_metadata, global_metadata)
11
- @api_metadata = api_metadata
12
- @global_metadata = global_metadata
10
+ def initialize(config = ::Rswag::Specs.config)
11
+ @config = config
13
12
  end
14
13
 
15
- def validate!(response, &block)
16
- validate_code!(response.code)
17
- validate_headers!(response.headers)
18
- validate_body!(response.body, &block)
19
- block.call(response) if block_given?
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
- if code.to_s != @api_metadata[:response][:code].to_s
26
- raise UnexpectedResponse, "Expected response code '#{code}' to match '#{@api_metadata[:response][:code]}'"
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
- header_schema = @api_metadata[:response][:headers]
32
- return if header_schema.nil?
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 = @api_metadata[: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
- begin
44
- validation_schema = response_schema
45
- .merge('$schema' => 'http://tempuri.org/rswag/specs/extended_schema')
46
- .merge(@global_metadata.slice(:definitions))
47
- JSON::Validator.validate!(validation_schema, body)
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.0
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 00:00:00.000000000 Z
11
+ date: 2017-07-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport