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 +4 -4
- data/lib/generators/rspec/swagger_generator.rb +2 -1
- data/lib/rswag/specs/configuration.rb +4 -0
- data/lib/rswag/specs/example_group_helpers.rb +23 -3
- data/lib/rswag/specs/request_factory.rb +24 -12
- data/lib/rswag/specs/response_validator.rb +27 -3
- data/lib/rswag/specs/swagger_formatter.rb +18 -4
- data/lib/rswag/specs.rb +1 -0
- data/lib/tasks/rswag-specs_tasks.rake +4 -2
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fa8f8005ed229944c19cb2e33e09eaebb95b8e32b9bef505a5e6b64b237bcbdc
|
|
4
|
+
data.tar.gz: 7eebf43807f0252c3168e08283b716c3dd9356f5d79fde1fcfc275377b5bc522
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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', '
|
|
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
|
-
|
|
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",
|
|
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",
|
|
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
|
|
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(/\{(.*?)\}/) {
|
|
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
|
|
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
|
|
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
|
|
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]
|
|
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
|
|
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
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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
|
|
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
|
-
|
|
35
|
+
header_schemas = (metadata[:response][:headers] || {})
|
|
36
|
+
expected = header_schemas.keys
|
|
36
37
|
expected.each do |name|
|
|
37
|
-
|
|
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
|
-
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
value.delete(:
|
|
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
|
@@ -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
|
|
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
|
|
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.
|
|
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:
|
|
13
|
+
date: 2023-04-24 00:00:00.000000000 Z
|
|
14
14
|
dependencies:
|
|
15
15
|
- !ruby/object:Gem::Dependency
|
|
16
16
|
name: activesupport
|