rswag-specs 2.7.0 → 2.9.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
  SHA256:
3
- metadata.gz: 989468cbcebd005e6bf95724400366cfa1b80e7d395bb6d1255bc12c283a98b2
4
- data.tar.gz: 949a822da86b5d6f3ead28cdd1262a093bcd8399875b1962f101c41634cc4c51
3
+ metadata.gz: fa8f8005ed229944c19cb2e33e09eaebb95b8e32b9bef505a5e6b64b237bcbdc
4
+ data.tar.gz: 7eebf43807f0252c3168e08283b716c3dd9356f5d79fde1fcfc275377b5bc522
5
5
  SHA512:
6
- metadata.gz: b4cb982b39359a8f69dd5ebf18287a2b4941349a202751d10f936b81014fce8ec99a24886e5de63c4fb1d4aba653893cf4ebff9f425f547be99b1ac3910e53ad
7
- data.tar.gz: 1064a439e5b8652c40a77eddd6d0837ea908780750187879bb8bdb11af67656ea6882a8a251bb8627001c7857ebad94c378d68432c1101e2e19647b5c7c29ae5
6
+ metadata.gz: e99187485ac197dad84d0f28b9bdec67c0e11b10efb5f2561a939e48852a6fc28505d2c92feb49a2969dd6a5a7919279986a05d9c3d09db2dc15581b085908ed
7
+ data.tar.gz: 1da746e08c42864094b13f36b72587f1d05b55922106ac1888accf16ac1bfae84c5fafb28dfa5952c47b477742b592bfff915743842523104f28e416859c1889
@@ -6,13 +6,14 @@ require 'rails/generators'
6
6
  module Rspec
7
7
  class SwaggerGenerator < ::Rails::Generators::NamedBase
8
8
  source_root File.expand_path('templates', __dir__)
9
+ class_option :spec_path, type: :string, default: 'requests'
9
10
 
10
11
  def setup
11
12
  @routes = Rswag::RouteParser.new(controller_path).routes
12
13
  end
13
14
 
14
15
  def create_spec_file
15
- template 'spec.rb', File.join('spec', 'requests', "#{controller_path}_spec.rb")
16
+ template 'spec.rb', File.join('spec', options['spec_path'], "#{controller_path}_spec.rb")
16
17
  end
17
18
 
18
19
  private
@@ -55,6 +55,10 @@ module Rswag
55
55
  doc = get_swagger_doc(name)
56
56
  doc[:openapi] || doc[:swagger]
57
57
  end
58
+
59
+ def swagger_strict_schema_validation
60
+ @swagger_strict_schema_validation ||= (@rspec_config.swagger_strict_schema_validation || false)
61
+ end
58
62
  end
59
63
 
60
64
  class ConfigurationError < StandardError; end
@@ -55,6 +55,18 @@ module Rswag
55
55
  end
56
56
  end
57
57
 
58
+
59
+ def request_body_example(value:, summary: nil, name: nil)
60
+ if metadata.key?(:operation)
61
+ metadata[:operation][:request_examples] ||= []
62
+ example = { value: value }
63
+ example[:summary] = summary if summary
64
+ # We need the examples to have a unique name for a set of examples, so just make the name the length if one isn't provided.
65
+ example[:name] = name || metadata[:operation][:request_examples].length()
66
+ metadata[:operation][:request_examples] << example
67
+ end
68
+ end
69
+
58
70
  def response(code, description, metadata = {}, &block)
59
71
  metadata[:response] = { code: code, description: description }
60
72
  context(description, metadata, &block)
@@ -103,14 +115,22 @@ module Rswag
103
115
  )
104
116
  end
105
117
 
106
- def run_test!(&block)
118
+ #
119
+ # Perform request and assert response matches swagger definitions
120
+ #
121
+ # @param options [Hash] options to pass to the `it` method
122
+ # @param &block [Proc] you can make additional assertions within that block
123
+ # @return [void]
124
+ def run_test!(**options, &block)
125
+ options[:rswag] = true unless options.key?(:rswag)
126
+
107
127
  if RSPEC_VERSION < 3
108
128
  ActiveSupport::Deprecation.warn('Rswag::Specs: WARNING: Support for RSpec 2.X will be dropped in v3.0')
109
129
  before do
110
130
  submit_request(example.metadata)
111
131
  end
112
132
 
113
- it "returns a #{metadata[:response][:code]} response", rswag: true do
133
+ it "returns a #{metadata[:response][:code]} response", **options do
114
134
  assert_response_matches_metadata(metadata)
115
135
  block.call(response) if block_given?
116
136
  end
@@ -119,7 +139,7 @@ module Rswag
119
139
  submit_request(example.metadata)
120
140
  end
121
141
 
122
- it "returns a #{metadata[:response][:code]} response", rswag: true do |example|
142
+ it "returns a #{metadata[:response][:code]} response", **options do |example|
123
143
  assert_response_matches_metadata(example.metadata, &block)
124
144
  example.instance_exec(response, &block) if block_given?
125
145
  end
@@ -34,7 +34,7 @@ module Rswag
34
34
  (operation_params + path_item_params + security_params)
35
35
  .map { |p| p['$ref'] ? resolve_parameter(p['$ref'], swagger_doc) : p }
36
36
  .uniq { |p| p[:name] }
37
- .reject { |p| p[:required] == false && !example.respond_to?(p[:name]) }
37
+ .reject { |p| p[:required] == false && !example.respond_to?(extract_getter(p)) }
38
38
  end
39
39
 
40
40
  def derive_security_params(metadata, swagger_doc)
@@ -106,7 +106,7 @@ module Rswag
106
106
  server = swagger_doc[:servers].first
107
107
  variables = {}
108
108
  server.fetch(:variables, {}).each_pair { |k,v| variables[k] = v[use_server] }
109
- base_path = server[:url].gsub(/\{(.*?)\}/) { |name| variables[name.to_sym] }
109
+ base_path = server[:url].gsub(/\{(.*?)\}/) { variables[$1.to_sym] }
110
110
  URI(base_path).path
111
111
  end
112
112
 
@@ -126,16 +126,16 @@ module Rswag
126
126
 
127
127
  request[:path] = template.tap do |path_template|
128
128
  parameters.select { |p| p[:in] == :path }.each do |p|
129
- unless example.respond_to?(p[:name])
129
+ unless example.respond_to?(extract_getter(p))
130
130
  raise ArgumentError.new("`#{p[:name].to_s}` parameter key present, but not defined within example group"\
131
131
  "(i. e `it` or `let` block)")
132
132
  end
133
- path_template.gsub!("{#{p[:name]}}", example.send(p[:name]).to_s)
133
+ path_template.gsub!("{#{p[:name]}}", example.send(extract_getter(p)).to_s)
134
134
  end
135
135
 
136
136
  parameters.select { |p| p[:in] == :query }.each_with_index do |p, i|
137
137
  path_template.concat(i.zero? ? '?' : '&')
138
- path_template.concat(build_query_string_part(p, example.send(p[:name]), swagger_doc))
138
+ path_template.concat(build_query_string_part(p, example.send(extract_getter(p)), swagger_doc))
139
139
  end
140
140
  end
141
141
  end
@@ -148,7 +148,7 @@ module Rswag
148
148
  style = param[:style]&.to_sym || :form
149
149
  explode = param[:explode].nil? ? true : param[:explode]
150
150
 
151
- case param[:schema][:type].to_sym
151
+ case param[:schema][:type]&.to_sym
152
152
  when :object
153
153
  case style
154
154
  when :deepObject
@@ -197,7 +197,7 @@ module Rswag
197
197
  def add_headers(request, metadata, swagger_doc, parameters, example)
198
198
  tuples = parameters
199
199
  .select { |p| p[:in] == :header }
200
- .map { |p| [p[:name], example.send(p[:name]).to_s] }
200
+ .map { |p| [p[:name], example.send(extract_getter(p)).to_s] }
201
201
 
202
202
  # Accept header
203
203
  produces = metadata[:operation][:produces] || swagger_doc[:produces]
@@ -213,14 +213,22 @@ module Rswag
213
213
  tuples << ['Content-Type', content_type]
214
214
  end
215
215
 
216
+ # Host header
217
+ host = metadata[:operation][:host] || swagger_doc[:host]
218
+ if host.present?
219
+ host = example.respond_to?(:'Host') ? example.send(:'Host') : host
220
+ tuples << ['Host', host]
221
+ end
222
+
216
223
  # Rails test infrastructure requires rack-formatted headers
217
224
  rack_formatted_tuples = tuples.map do |pair|
218
225
  [
219
226
  case pair[0]
220
- when 'Accept' then 'HTTP_ACCEPT'
221
- when 'Content-Type' then 'CONTENT_TYPE'
222
- when 'Authorization' then 'HTTP_AUTHORIZATION'
223
- else pair[0]
227
+ when 'Accept' then 'HTTP_ACCEPT'
228
+ when 'Content-Type' then 'CONTENT_TYPE'
229
+ when 'Authorization' then 'HTTP_AUTHORIZATION'
230
+ when 'Host' then 'HTTP_HOST'
231
+ else pair[0]
224
232
  end,
225
233
  pair[1]
226
234
  ]
@@ -247,7 +255,7 @@ module Rswag
247
255
  # PROS: simple to implement, CONS: serialization/deserialization is bypassed in test
248
256
  tuples = parameters
249
257
  .select { |p| p[:in] == :formData }
250
- .map { |p| [p[:name], example.send(p[:name])] }
258
+ .map { |p| [p[:name], example.send(extract_getter(p))] }
251
259
  Hash[tuples]
252
260
  end
253
261
 
@@ -264,6 +272,10 @@ module Rswag
264
272
  def doc_version(doc)
265
273
  doc[:openapi] || doc[:swagger] || '3'
266
274
  end
275
+
276
+ def extract_getter(parameter)
277
+ parameter[:getter] || parameter[:name]
278
+ end
267
279
  end
268
280
 
269
281
  class MissingParameterError < StandardError
@@ -32,9 +32,22 @@ module Rswag
32
32
  end
33
33
 
34
34
  def validate_headers!(metadata, headers)
35
- expected = (metadata[:response][:headers] || {}).keys
35
+ header_schemas = (metadata[:response][:headers] || {})
36
+ expected = header_schemas.keys
36
37
  expected.each do |name|
37
- raise UnexpectedResponse, "Expected response header #{name} to be present" if headers[name.to_s].nil?
38
+ nullable_attribute = header_schemas.dig(name.to_s, :schema, :nullable)
39
+ required_attribute = header_schemas.dig(name.to_s, :required)
40
+
41
+ is_nullable = nullable_attribute.nil? ? false : nullable_attribute
42
+ is_required = required_attribute.nil? ? true : required_attribute
43
+
44
+ if headers.exclude?(name.to_s) && is_required
45
+ raise UnexpectedResponse, "Expected response header #{name} to be present"
46
+ end
47
+
48
+ if headers.include?(name.to_s) && headers[name.to_s].nil? && !is_nullable
49
+ raise UnexpectedResponse, "Expected response header #{name} to not be null"
50
+ end
38
51
  end
39
52
  end
40
53
 
@@ -49,7 +62,9 @@ module Rswag
49
62
  .merge('$schema' => 'http://tempuri.org/rswag/specs/extended_schema')
50
63
  .merge(schemas)
51
64
 
52
- errors = JSON::Validator.fully_validate(validation_schema, body)
65
+ validation_options = validation_options_from(metadata)
66
+
67
+ errors = JSON::Validator.fully_validate(validation_schema, body, validation_options)
53
68
  return unless errors.any?
54
69
 
55
70
  raise UnexpectedResponse,
@@ -57,6 +72,15 @@ module Rswag
57
72
  "Response body: #{JSON.pretty_generate(JSON.parse(body))}"
58
73
  end
59
74
 
75
+ def validation_options_from(metadata)
76
+ is_strict = !!metadata.fetch(
77
+ :swagger_strict_schema_validation,
78
+ @config.swagger_strict_schema_validation
79
+ )
80
+
81
+ { strict: is_strict }
82
+ end
83
+
60
84
  def definitions_or_component_schemas(swagger_doc, version)
61
85
  if version.start_with?('2')
62
86
  swagger_doc.slice(:definitions)
@@ -58,12 +58,23 @@ module Rswag
58
58
  if is_hash && value[:parameters]
59
59
  schema_param = value[:parameters]&.find { |p| (p[:in] == :body || p[:in] == :formData) && p[:schema] }
60
60
  mime_list = value[:consumes] || doc[:consumes]
61
+
61
62
  if value && schema_param && mime_list
62
63
  value[:requestBody] = { content: {} } unless value.dig(:requestBody, :content)
63
64
  value[:requestBody][:required] = true if schema_param[:required]
64
65
  value[:requestBody][:description] = schema_param[:description] if schema_param[:description]
66
+ examples = value.dig(:request_examples)
65
67
  mime_list.each do |mime|
66
68
  value[:requestBody][:content][mime] = { schema: schema_param[:schema] }
69
+ if examples
70
+ value[:requestBody][:content][mime][:examples] ||= {}
71
+ examples.map do |example|
72
+ value[:requestBody][:content][mime][:examples][example[:name]] = {
73
+ summary: example[:summary] || value[:summary],
74
+ value: example[:value]
75
+ }
76
+ end
77
+ end
67
78
  end
68
79
  end
69
80
 
@@ -137,7 +148,7 @@ module Rswag
137
148
 
138
149
  target_node[:content] ||= {}
139
150
  mime_list.each do |mime_type|
140
- # TODO upgrade to have content-type specific schema
151
+ # TODO: upgrade to have content-type specific schema
141
152
  (target_node[:content][mime_type] ||= {}).merge!(schema: schema)
142
153
  end
143
154
  end
@@ -197,9 +208,12 @@ module Rswag
197
208
  end
198
209
 
199
210
  def remove_invalid_operation_keys!(value)
200
- is_hash = value.is_a?(Hash)
201
- value.delete(:consumes) if is_hash && value[:consumes]
202
- value.delete(:produces) if is_hash && value[:produces]
211
+ return unless value.is_a?(Hash)
212
+
213
+ value.delete(:consumes) if value[:consumes]
214
+ value.delete(:produces) if value[:produces]
215
+ value.delete(:request_examples) if value[:request_examples]
216
+ value[:parameters].each { |p| p.delete(:getter) } if value[:parameters]
203
217
  end
204
218
  end
205
219
  end
data/lib/rswag/specs.rb CHANGED
@@ -14,6 +14,7 @@ module Rswag
14
14
  c.add_setting :swagger_docs
15
15
  c.add_setting :swagger_dry_run
16
16
  c.add_setting :swagger_format
17
+ c.add_setting :swagger_strict_schema_validation
17
18
  c.extend ExampleGroupHelpers, type: :request
18
19
  c.include ExampleHelpers, type: :request
19
20
  end
@@ -16,11 +16,13 @@ namespace :rswag do
16
16
  ''
17
17
  )
18
18
 
19
+ t.rspec_opts = [additional_rspec_opts]
20
+
19
21
  if Rswag::Specs::RSPEC_VERSION > 2 && Rswag::Specs.config.swagger_dry_run
20
- t.rspec_opts = ['--format Rswag::Specs::SwaggerFormatter', '--dry-run', '--order defined'] << additional_rspec_opts
22
+ t.rspec_opts += ['--format Rswag::Specs::SwaggerFormatter', '--dry-run', '--order defined']
21
23
  else
22
24
  ActiveSupport::Deprecation.warn('Rswag::Specs: WARNING: Support for RSpec 2.X will be dropped in v3.0')
23
- t.rspec_opts = ['--format Rswag::Specs::SwaggerFormatter', '--order defined']
25
+ t.rspec_opts += ['--format Rswag::Specs::SwaggerFormatter', '--order defined']
24
26
  end
25
27
  end
26
28
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rswag-specs
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.7.0
4
+ version: 2.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Richie Morris
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2022-10-19 00:00:00.000000000 Z
13
+ date: 2023-04-24 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activesupport