rswag-specs 2.8.0 → 2.10.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: cfedb2177ee21788aa710db72fbc573230a51c84877bd90edff99b8a1fc9967c
4
- data.tar.gz: 842d0dba7c2dcd4aa59ec015928c825da6ef1cf892859e492589d540b003c181
3
+ metadata.gz: 6d30046aab2de448f6194ded5849b09684c36be6e1b5e703858647bca6bb3301
4
+ data.tar.gz: 24bd271875ee9011313a2866930c7b045175638564d35e5816e5b3ad75fb59d7
5
5
  SHA512:
6
- metadata.gz: 5640b7930547aa45a3b2662f8fad08508063d1c9726c2f72d6e2f15f6eab32408472e7645230b30a5eec74ae223cfae5e87e9d26311cc609aa543c90d8234aef
7
- data.tar.gz: 98e797ce8b1babf942ba387cffbe6eec52994047543c669b9293631bc08bfc1695b9d3c1ca572f3ec9357e7649bf601f0df81bf28590ee605b501f7546aae8f1
6
+ metadata.gz: b61f3ac5b3d790eadc1d424ec2bebc967fbd97cafb22de3a4d7186c421d83bd4283b6bf0c63c450f749f969b8a5244490a74e5355179b62f615847e26759a53a
7
+ data.tar.gz: 03af0fb0ed593daa9e6e9d9102aa74445bffbe65fa20a473b157c90b7a82120660d23cee13b84c9986d3ecc601525a3c5c6e68969a4b37e109433a5e1d1be651
@@ -0,0 +1,17 @@
1
+ RSpec:
2
+ Language:
3
+ ExampleGroups:
4
+ Regular:
5
+ - path
6
+ - response
7
+ - get
8
+ - post
9
+ - patch
10
+ - put
11
+ - delete
12
+ - head
13
+ - options
14
+ - trace
15
+ Examples:
16
+ Regular:
17
+ - run_test!
@@ -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
@@ -13,9 +13,9 @@ module Rswag
13
13
  end
14
14
 
15
15
  [:get, :post, :patch, :put, :delete, :head, :options, :trace].each do |verb|
16
- define_method(verb) do |summary, &block|
17
- api_metadata = { operation: { verb: verb, summary: summary } }
18
- describe(verb, api_metadata, &block)
16
+ define_method(verb) do |summary, **metadata, &block|
17
+ api_metadata = { operation: { verb: verb, summary: summary } }.deep_merge(metadata)
18
+ describe(verb, **api_metadata, &block)
19
19
  end
20
20
  end
21
21
 
@@ -55,17 +55,16 @@ 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)
58
+ def request_body_example(value:, summary: nil, name: nil)
59
+ if metadata.key?(:operation)
61
60
  metadata[:operation][:request_examples] ||= []
62
- example = { value: value }
63
- example[:summary] = summary if summary
61
+ example = { value: value }
62
+ example[:summary] = summary if summary
64
63
  # 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
64
  example[:name] = name || metadata[:operation][:request_examples].length()
66
65
  metadata[:operation][:request_examples] << example
67
- end
68
- end
66
+ end
67
+ end
69
68
 
70
69
  def response(code, description, metadata = {}, &block)
71
70
  metadata[:response] = { code: code, description: description }
@@ -115,14 +114,27 @@ module Rswag
115
114
  )
116
115
  end
117
116
 
118
- def run_test!(&block)
117
+ #
118
+ # Perform request and assert response matches swagger definitions
119
+ #
120
+ # @param description [String] description of the test
121
+ # @param args [Array] arguments to pass to the `it` method
122
+ # @param options [Hash] options to pass to the `it` method
123
+ # @param &block [Proc] you can make additional assertions within that block
124
+ # @return [void]
125
+ def run_test!(description = nil, *args, **options, &block)
126
+ # rswag metadata value defaults to true
127
+ options[:rswag] = true unless options.key?(:rswag)
128
+
129
+ description ||= "returns a #{metadata[:response][:code]} response"
130
+
119
131
  if RSPEC_VERSION < 3
120
132
  ActiveSupport::Deprecation.warn('Rswag::Specs: WARNING: Support for RSpec 2.X will be dropped in v3.0')
121
133
  before do
122
134
  submit_request(example.metadata)
123
135
  end
124
136
 
125
- it "returns a #{metadata[:response][:code]} response", rswag: true do
137
+ it description, *args, **options do
126
138
  assert_response_matches_metadata(metadata)
127
139
  block.call(response) if block_given?
128
140
  end
@@ -131,7 +143,7 @@ module Rswag
131
143
  submit_request(example.metadata)
132
144
  end
133
145
 
134
- it "returns a #{metadata[:response][:code]} response", rswag: true do |example|
146
+ it description, *args, **options do |example|
135
147
  assert_response_matches_metadata(example.metadata, &block)
136
148
  example.instance_exec(response, &block) if block_given?
137
149
  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,22 +126,23 @@ 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
142
142
 
143
143
  def build_query_string_part(param, value, swagger_doc)
144
144
  name = param[:name]
145
+ escaped_name = CGI.escape(name.to_s)
145
146
 
146
147
  # OAS 3: https://swagger.io/docs/specification/serialization/
147
148
  if swagger_doc && doc_version(swagger_doc).start_with?('3') && param[:schema]
@@ -157,20 +158,20 @@ module Rswag
157
158
  if explode
158
159
  return value.to_query
159
160
  else
160
- return "#{CGI.escape(name.to_s)}=" + value.to_a.flatten.map{|v| CGI.escape(v.to_s) }.join(',')
161
+ return "#{escaped_name}=" + value.to_a.flatten.map{|v| CGI.escape(v.to_s) }.join(',')
161
162
  end
162
163
  end
163
164
  when :array
164
165
  case explode
165
166
  when true
166
- return value.to_a.flatten.map{|v| "#{CGI.escape(name.to_s)}=#{CGI.escape(v.to_s)}"}.join('&')
167
+ return value.to_a.flatten.map{|v| "#{escaped_name}=#{CGI.escape(v.to_s)}"}.join('&')
167
168
  else
168
169
  separator = case style
169
170
  when :form then ','
170
171
  when :spaceDelimited then '%20'
171
172
  when :pipeDelimited then '|'
172
173
  end
173
- return "#{CGI.escape(name.to_s)}=" + value.to_a.flatten.map{|v| CGI.escape(v.to_s) }.join(separator)
174
+ return "#{escaped_name}=" + value.to_a.flatten.map{|v| CGI.escape(v.to_s) }.join(separator)
174
175
  end
175
176
  else
176
177
  return "#{name}=#{value}"
@@ -178,7 +179,7 @@ module Rswag
178
179
  end
179
180
 
180
181
  type = param[:type] || param.dig(:schema, :type)
181
- return "#{name}=#{value}" unless type&.to_sym == :array
182
+ return "#{escaped_name}=#{CGI.escape(value.to_s)}" unless type&.to_sym == :array
182
183
 
183
184
  case param[:collectionFormat]
184
185
  when :ssv
@@ -197,7 +198,7 @@ module Rswag
197
198
  def add_headers(request, metadata, swagger_doc, parameters, example)
198
199
  tuples = parameters
199
200
  .select { |p| p[:in] == :header }
200
- .map { |p| [p[:name], example.send(p[:name]).to_s] }
201
+ .map { |p| [p[:name], example.send(extract_getter(p)).to_s] }
201
202
 
202
203
  # Accept header
203
204
  produces = metadata[:operation][:produces] || swagger_doc[:produces]
@@ -255,7 +256,7 @@ module Rswag
255
256
  # PROS: simple to implement, CONS: serialization/deserialization is bypassed in test
256
257
  tuples = parameters
257
258
  .select { |p| p[:in] == :formData }
258
- .map { |p| [p[:name], example.send(p[:name])] }
259
+ .map { |p| [p[:name], example.send(extract_getter(p))] }
259
260
  Hash[tuples]
260
261
  end
261
262
 
@@ -272,6 +273,10 @@ module Rswag
272
273
  def doc_version(doc)
273
274
  doc[:openapi] || doc[:swagger] || '3'
274
275
  end
276
+
277
+ def extract_getter(parameter)
278
+ parameter[:getter] || parameter[:name]
279
+ end
275
280
  end
276
281
 
277
282
  class MissingParameterError < StandardError
@@ -62,7 +62,9 @@ module Rswag
62
62
  .merge('$schema' => 'http://tempuri.org/rswag/specs/extended_schema')
63
63
  .merge(schemas)
64
64
 
65
- 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)
66
68
  return unless errors.any?
67
69
 
68
70
  raise UnexpectedResponse,
@@ -70,6 +72,15 @@ module Rswag
70
72
  "Response body: #{JSON.pretty_generate(JSON.parse(body))}"
71
73
  end
72
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
+
73
84
  def definitions_or_component_schemas(swagger_doc, version)
74
85
  if version.start_with?('2')
75
86
  swagger_doc.slice(:definitions)
@@ -58,6 +58,7 @@ 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]
@@ -147,7 +148,7 @@ module Rswag
147
148
 
148
149
  target_node[:content] ||= {}
149
150
  mime_list.each do |mime_type|
150
- # TODO upgrade to have content-type specific schema
151
+ # TODO: upgrade to have content-type specific schema
151
152
  (target_node[:content][mime_type] ||= {}).merge!(schema: schema)
152
153
  end
153
154
  end
@@ -207,10 +208,12 @@ module Rswag
207
208
  end
208
209
 
209
210
  def remove_invalid_operation_keys!(value)
210
- is_hash = value.is_a?(Hash)
211
- value.delete(:consumes) if is_hash && value[:consumes]
212
- value.delete(:produces) if is_hash && value[:produces]
213
- value.delete(:request_examples) if is_hash && value[:request_examples]
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]
214
217
  end
215
218
  end
216
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.8.0
4
+ version: 2.10.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-11-16 00:00:00.000000000 Z
13
+ date: 2023-07-13 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activesupport
@@ -109,6 +109,7 @@ executables: []
109
109
  extensions: []
110
110
  extra_rdoc_files: []
111
111
  files:
112
+ - ".rubocop_rspec_alias_config.yml"
112
113
  - MIT-LICENSE
113
114
  - Rakefile
114
115
  - lib/generators/rspec/USAGE