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 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