rswag-specs 2.7.0 → 2.9.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
  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