rspec-openapi 0.16.0 → 0.17.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/.rubocop.yml +1 -1
- data/.rubocop_todo.yml +13 -12
- data/Gemfile +9 -0
- data/README.md +9 -2
- data/lib/rspec/openapi/extractors/hanami.rb +118 -0
- data/lib/rspec/openapi/extractors/rack.rb +31 -0
- data/lib/rspec/openapi/extractors/rails.rb +58 -0
- data/lib/rspec/openapi/extractors.rb +5 -0
- data/lib/rspec/openapi/minitest_hooks.rb +1 -1
- data/lib/rspec/openapi/record.rb +1 -0
- data/lib/rspec/openapi/record_builder.rb +9 -69
- data/lib/rspec/openapi/result_recorder.rb +16 -5
- data/lib/rspec/openapi/rspec_hooks.rb +1 -1
- data/lib/rspec/openapi/schema_builder.rb +1 -0
- data/lib/rspec/openapi/schema_merger.rb +8 -1
- data/lib/rspec/openapi/shared_hooks.rb +15 -0
- data/lib/rspec/openapi/version.rb +1 -1
- data/lib/rspec/openapi.rb +20 -1
- data/rspec-openapi.gemspec +1 -0
- metadata +22 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '04058c6bcfd8f9ec499d5c7eb82da1e1123bfd9a666056384cda5a39aa70e4b7'
|
4
|
+
data.tar.gz: a4df891dbf2ba621f1cf3a78dbfcaab22da75583a40ea974935ad6e9aad1dfa0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9764345746f2c8133a18580eb1dffcf97709dd6e077b86e326f62604daffc856fd0c67e2735c7e8dbc16ae56828d9d3d9222cbc75f2f4a2e63789308df6ea4fd
|
7
|
+
data.tar.gz: e54bf06b9d2bd91952b56e91cc2cfcbae1cfb0998ab00a7019c4176b114a3652ef3e3323bf922530c2cf8e207742df6ff6fb6764be680ba8db7a8910bf260cdc
|
data/.rubocop.yml
CHANGED
data/.rubocop_todo.yml
CHANGED
@@ -1,35 +1,35 @@
|
|
1
1
|
# This configuration was generated by
|
2
2
|
# `rubocop --auto-gen-config`
|
3
|
-
# on 2024-
|
3
|
+
# on 2024-04-12 14:06:44 UTC using RuboCop version 1.62.1.
|
4
4
|
# The point is for the user to remove these configuration records
|
5
5
|
# one by one as the offenses are removed from the code base.
|
6
6
|
# Note that changes in the inspected code, or installation of new
|
7
7
|
# versions of RuboCop, may require this file to be generated again.
|
8
8
|
|
9
|
-
# Offense count:
|
9
|
+
# Offense count: 13
|
10
10
|
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
|
11
11
|
Metrics/AbcSize:
|
12
|
-
Max:
|
12
|
+
Max: 47
|
13
13
|
|
14
|
-
# Offense count:
|
14
|
+
# Offense count: 1
|
15
15
|
# Configuration parameters: CountComments, CountAsOne.
|
16
16
|
Metrics/ClassLength:
|
17
|
-
Max:
|
17
|
+
Max: 195
|
18
18
|
|
19
|
-
# Offense count:
|
19
|
+
# Offense count: 9
|
20
20
|
# Configuration parameters: AllowedMethods, AllowedPatterns.
|
21
21
|
Metrics/CyclomaticComplexity:
|
22
|
-
Max:
|
22
|
+
Max: 12
|
23
23
|
|
24
|
-
# Offense count:
|
24
|
+
# Offense count: 22
|
25
25
|
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
|
26
26
|
Metrics/MethodLength:
|
27
|
-
Max:
|
27
|
+
Max: 36
|
28
28
|
|
29
|
-
# Offense count:
|
29
|
+
# Offense count: 5
|
30
30
|
# Configuration parameters: AllowedMethods, AllowedPatterns.
|
31
31
|
Metrics/PerceivedComplexity:
|
32
|
-
Max:
|
32
|
+
Max: 12
|
33
33
|
|
34
34
|
# Offense count: 1
|
35
35
|
# Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers, AllowedPatterns.
|
@@ -39,7 +39,7 @@ Naming/VariableNumber:
|
|
39
39
|
Exclude:
|
40
40
|
- 'spec/integration_tests/roda_test.rb'
|
41
41
|
|
42
|
-
# Offense count:
|
42
|
+
# Offense count: 7
|
43
43
|
# Configuration parameters: AllowedConstants.
|
44
44
|
Style/Documentation:
|
45
45
|
Exclude:
|
@@ -49,3 +49,4 @@ Style/Documentation:
|
|
49
49
|
- 'lib/rspec/openapi/minitest_hooks.rb'
|
50
50
|
- 'lib/rspec/openapi/result_recorder.rb'
|
51
51
|
- 'lib/rspec/openapi/schema_file.rb'
|
52
|
+
- 'lib/rspec/openapi/shared_hooks.rb'
|
data/Gemfile
CHANGED
@@ -6,7 +6,16 @@ source 'https://rubygems.org'
|
|
6
6
|
gemspec
|
7
7
|
|
8
8
|
gem 'rails', ENV['RAILS_VERSION'] || '6.0.3.7'
|
9
|
+
|
10
|
+
if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.0.0')
|
11
|
+
gem 'hanami', ENV['HANAMI_VERSION'] || '2.1.0'
|
12
|
+
gem 'hanami-controller', ENV['HANAMI_VERSION'] || '2.1.0'
|
13
|
+
gem 'hanami-router', ENV['HANAMI_VERSION'] || '2.1.0'
|
14
|
+
end
|
15
|
+
|
9
16
|
gem 'roda'
|
17
|
+
|
18
|
+
gem 'rails-dom-testing', '~> 2.2'
|
10
19
|
gem 'rspec-rails'
|
11
20
|
|
12
21
|
group :test do
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# rspec-openapi [](https://
|
1
|
+
# rspec-openapi [](https://rubygems.org/gems/rspec-openapi) [](https://github.com/exoego/rspec-openapi/actions/workflows/test.yml) [](https://codecov.io/gh/exoego/rspec-openapi) [](https://www.ruby-toolbox.com/projects/rspec-openapi)
|
2
2
|
|
3
3
|
Generate OpenAPI schema from RSpec request specs.
|
4
4
|
|
@@ -97,7 +97,7 @@ paths:
|
|
97
97
|
|
98
98
|
and the schema file can be used as an input of [Swagger UI](https://github.com/swagger-api/swagger-ui) or [Redoc](https://github.com/Redocly/redoc).
|
99
99
|
|
100
|
-

|
100
|
+

|
101
101
|
|
102
102
|
|
103
103
|
### Configuration
|
@@ -190,6 +190,13 @@ RSpec::OpenAPI.ignored_path_params = %i[controller action format]
|
|
190
190
|
# In that case, you can specify the paths to ignore.
|
191
191
|
# String or Regexp is acceptable.
|
192
192
|
RSpec::OpenAPI.ignored_paths = ["/admin/full/path/", Regexp.new("^/_internal/")]
|
193
|
+
|
194
|
+
# Your custom post-processing hook (like unrandomizing IDs)
|
195
|
+
RSpec::OpenAPI.post_process_hook = -> (path, records, spec) do
|
196
|
+
RSpec::OpenAPI::HashHelper.matched_paths(spec, 'paths.*.*.responses.*.content.*.*.*.id').each do |paths|
|
197
|
+
spec.dig(*paths[0..-2]).merge!(id: '123')
|
198
|
+
end
|
199
|
+
end
|
193
200
|
```
|
194
201
|
|
195
202
|
### Can I use rspec-openapi with `$ref` to minimize duplication of schema?
|
@@ -0,0 +1,118 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'dry/inflector'
|
4
|
+
require 'hanami'
|
5
|
+
|
6
|
+
# https://github.com/hanami/router/blob/97f75b8529574bd4ff23165460e82a6587bc323c/lib/hanami/router/inspector.rb#L13
|
7
|
+
class Inspector
|
8
|
+
attr_accessor :routes, :inflector
|
9
|
+
|
10
|
+
def initialize(routes: [])
|
11
|
+
@routes = routes
|
12
|
+
@inflector = Dry::Inflector.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_route(route)
|
16
|
+
routes.push(route)
|
17
|
+
end
|
18
|
+
|
19
|
+
def call(verb, path)
|
20
|
+
route = routes.find { |r| r.http_method == verb && r.path == path }
|
21
|
+
|
22
|
+
if route.to.is_a?(Proc)
|
23
|
+
{
|
24
|
+
tags: [],
|
25
|
+
summary: "#{verb} #{path}",
|
26
|
+
}
|
27
|
+
else
|
28
|
+
data = route.to.split('.')
|
29
|
+
|
30
|
+
{
|
31
|
+
tags: [inflector.classify(data[0])],
|
32
|
+
summary: data[1],
|
33
|
+
}
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
InspectorAnalyzer = Inspector.new
|
39
|
+
|
40
|
+
# Add default parameter to load inspector before test cases run
|
41
|
+
module InspectorAnalyzerPrepender
|
42
|
+
def router(inspector: InspectorAnalyzer)
|
43
|
+
super
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
Hanami::Slice::ClassMethods.prepend(InspectorAnalyzerPrepender)
|
48
|
+
|
49
|
+
# Extractor for hanami
|
50
|
+
class << RSpec::OpenAPI::Extractors::Hanami = Object.new
|
51
|
+
# @param [RSpec::ExampleGroups::*] context
|
52
|
+
# @param [RSpec::Core::Example] example
|
53
|
+
# @return Array
|
54
|
+
def request_attributes(request, example)
|
55
|
+
metadata = example.metadata[:openapi] || {}
|
56
|
+
summary = metadata[:summary] || RSpec::OpenAPI.summary_builder.call(example)
|
57
|
+
tags = metadata[:tags] || RSpec::OpenAPI.tags_builder.call(example)
|
58
|
+
operation_id = metadata[:operation_id]
|
59
|
+
required_request_params = metadata[:required_request_params] || []
|
60
|
+
security = metadata[:security]
|
61
|
+
description = metadata[:description] || RSpec::OpenAPI.description_builder.call(example)
|
62
|
+
deprecated = metadata[:deprecated]
|
63
|
+
path = request.path
|
64
|
+
|
65
|
+
route = Hanami.app.router.recognize(request.path, method: request.method)
|
66
|
+
|
67
|
+
raw_path_params = route.params.filter { |_key, value| number_or_nil(value) }
|
68
|
+
|
69
|
+
result = InspectorAnalyzer.call(request.method, add_id(path, route))
|
70
|
+
|
71
|
+
summary ||= result[:summary]
|
72
|
+
tags ||= result[:tags]
|
73
|
+
path = add_openapi_id(path, route)
|
74
|
+
|
75
|
+
raw_path_params = raw_path_params.slice(*(raw_path_params.keys - RSpec::OpenAPI.ignored_path_params))
|
76
|
+
|
77
|
+
[path, summary, tags, operation_id, required_request_params, raw_path_params, description, security, deprecated]
|
78
|
+
end
|
79
|
+
|
80
|
+
# @param [RSpec::ExampleGroups::*] context
|
81
|
+
def request_response(context)
|
82
|
+
request = ActionDispatch::Request.new(context.last_request.env)
|
83
|
+
request.body.rewind if request.body.respond_to?(:rewind)
|
84
|
+
response = ActionDispatch::TestResponse.new(*context.last_response.to_a)
|
85
|
+
|
86
|
+
[request, response]
|
87
|
+
end
|
88
|
+
|
89
|
+
def add_id(path, route)
|
90
|
+
return path if route.params.empty?
|
91
|
+
|
92
|
+
route.params.each_pair do |key, value|
|
93
|
+
next unless number_or_nil(value)
|
94
|
+
|
95
|
+
path = path.sub("/#{value}", "/:#{key}")
|
96
|
+
end
|
97
|
+
|
98
|
+
path
|
99
|
+
end
|
100
|
+
|
101
|
+
def add_openapi_id(path, route)
|
102
|
+
return path if route.params.empty?
|
103
|
+
|
104
|
+
route.params.each_pair do |key, value|
|
105
|
+
next unless number_or_nil(value)
|
106
|
+
|
107
|
+
path = path.sub("/#{value}", "/{#{key}}")
|
108
|
+
end
|
109
|
+
|
110
|
+
path
|
111
|
+
end
|
112
|
+
|
113
|
+
def number_or_nil(string)
|
114
|
+
Integer(string || '')
|
115
|
+
rescue ArgumentError
|
116
|
+
nil
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Extractor for rack
|
4
|
+
class << RSpec::OpenAPI::Extractors::Rack = Object.new
|
5
|
+
# @param [RSpec::ExampleGroups::*] context
|
6
|
+
# @param [RSpec::Core::Example] example
|
7
|
+
# @return Array
|
8
|
+
def request_attributes(request, example)
|
9
|
+
metadata = example.metadata[:openapi] || {}
|
10
|
+
summary = metadata[:summary] || RSpec::OpenAPI.summary_builder.call(example)
|
11
|
+
tags = metadata[:tags] || RSpec::OpenAPI.tags_builder.call(example)
|
12
|
+
operation_id = metadata[:operation_id]
|
13
|
+
required_request_params = metadata[:required_request_params] || []
|
14
|
+
security = metadata[:security]
|
15
|
+
description = metadata[:description] || RSpec::OpenAPI.description_builder.call(example)
|
16
|
+
deprecated = metadata[:deprecated]
|
17
|
+
raw_path_params = request.path_parameters
|
18
|
+
path = request.path
|
19
|
+
summary ||= "#{request.method} #{path}"
|
20
|
+
[path, summary, tags, operation_id, required_request_params, raw_path_params, description, security, deprecated]
|
21
|
+
end
|
22
|
+
|
23
|
+
# @param [RSpec::ExampleGroups::*] context
|
24
|
+
def request_response(context)
|
25
|
+
request = ActionDispatch::Request.new(context.last_request.env)
|
26
|
+
request.body.rewind if request.body.respond_to?(:rewind)
|
27
|
+
response = ActionDispatch::TestResponse.new(*context.last_response.to_a)
|
28
|
+
|
29
|
+
[request, response]
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Extractor for rails
|
4
|
+
class << RSpec::OpenAPI::Extractors::Rails = Object.new
|
5
|
+
# @param [RSpec::ExampleGroups::*] context
|
6
|
+
# @param [RSpec::Core::Example] example
|
7
|
+
# @return Array
|
8
|
+
def request_attributes(request, example)
|
9
|
+
metadata = example.metadata[:openapi] || {}
|
10
|
+
summary = metadata[:summary] || RSpec::OpenAPI.summary_builder.call(example)
|
11
|
+
tags = metadata[:tags] || RSpec::OpenAPI.tags_builder.call(example)
|
12
|
+
operation_id = metadata[:operation_id]
|
13
|
+
required_request_params = metadata[:required_request_params] || []
|
14
|
+
security = metadata[:security]
|
15
|
+
description = metadata[:description] || RSpec::OpenAPI.description_builder.call(example)
|
16
|
+
deprecated = metadata[:deprecated]
|
17
|
+
raw_path_params = request.path_parameters
|
18
|
+
|
19
|
+
# Reverse the destructive modification by Rails https://github.com/rails/rails/blob/v6.0.3.4/actionpack/lib/action_dispatch/journey/router.rb#L33-L41
|
20
|
+
fixed_request = request.dup
|
21
|
+
fixed_request.path_info = File.join(request.script_name, request.path_info) if request.script_name.present?
|
22
|
+
|
23
|
+
route, path = find_rails_route(fixed_request)
|
24
|
+
raise "No route matched for #{fixed_request.request_method} #{fixed_request.path_info}" if route.nil?
|
25
|
+
|
26
|
+
path = path.delete_suffix('(.:format)')
|
27
|
+
summary ||= route.requirements[:action]
|
28
|
+
tags ||= [route.requirements[:controller]&.classify].compact
|
29
|
+
# :controller and :action always exist. :format is added when routes is configured as such.
|
30
|
+
# TODO: Use .except(:controller, :action, :format) when we drop support for Ruby 2.x
|
31
|
+
raw_path_params = raw_path_params.slice(*(raw_path_params.keys - RSpec::OpenAPI.ignored_path_params))
|
32
|
+
|
33
|
+
summary ||= "#{request.method} #{path}"
|
34
|
+
|
35
|
+
[path, summary, tags, operation_id, required_request_params, raw_path_params, description, security, deprecated]
|
36
|
+
end
|
37
|
+
|
38
|
+
# @param [RSpec::ExampleGroups::*] context
|
39
|
+
def request_response(context)
|
40
|
+
[context.request, context.response]
|
41
|
+
end
|
42
|
+
|
43
|
+
# @param [ActionDispatch::Request] request
|
44
|
+
def find_rails_route(request, app: Rails.application, path_prefix: '')
|
45
|
+
app.routes.router.recognize(request) do |route|
|
46
|
+
path = route.path.spec.to_s
|
47
|
+
if route.app.matches?(request)
|
48
|
+
if route.app.engine?
|
49
|
+
route, path = find_rails_route(request, app: route.app.app, path_prefix: path)
|
50
|
+
next if route.nil?
|
51
|
+
end
|
52
|
+
return [route, path_prefix + path]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
nil
|
57
|
+
end
|
58
|
+
end
|
@@ -13,7 +13,7 @@ module RSpec::OpenAPI::Minitest
|
|
13
13
|
human_name = name.sub(/^test_/, '').gsub('_', ' ')
|
14
14
|
example = Example.new(self, human_name, {}, file_path)
|
15
15
|
path = RSpec::OpenAPI.path.then { |p| p.is_a?(Proc) ? p.call(example) : p }
|
16
|
-
record = RSpec::OpenAPI::RecordBuilder.build(self, example: example)
|
16
|
+
record = RSpec::OpenAPI::RecordBuilder.build(self, example: example, extractor: SharedHooks.find_extractor)
|
17
17
|
RSpec::OpenAPI.path_records[path] << record if record
|
18
18
|
end
|
19
19
|
result
|
data/lib/rspec/openapi/record.rb
CHANGED
@@ -14,6 +14,7 @@ RSpec::OpenAPI::Record = Struct.new(
|
|
14
14
|
:operation_id, # @param [String] - "request-1234"
|
15
15
|
:description, # @param [String] - "returns a status"
|
16
16
|
:security, # @param [Array] - [{securityScheme1: []}]
|
17
|
+
:deprecated, # @param [Boolean] - true
|
17
18
|
:status, # @param [Integer] - 200
|
18
19
|
:response_body, # @param [Object] - {"status" => "ok"}
|
19
20
|
:response_headers, # @param [Array] - [["header_key1", "header_value1"], ["header_key2", "header_value2"]]
|
@@ -7,12 +7,12 @@ class << RSpec::OpenAPI::RecordBuilder = Object.new
|
|
7
7
|
# @param [RSpec::ExampleGroups::*] context
|
8
8
|
# @param [RSpec::Core::Example] example
|
9
9
|
# @return [RSpec::OpenAPI::Record,nil]
|
10
|
-
def build(context, example:)
|
11
|
-
request, response =
|
10
|
+
def build(context, example:, extractor:)
|
11
|
+
request, response = extractor.request_response(context)
|
12
12
|
return if request.nil?
|
13
13
|
|
14
|
-
path, summary, tags, operation_id, required_request_params, raw_path_params, description, security =
|
15
|
-
|
14
|
+
path, summary, tags, operation_id, required_request_params, raw_path_params, description, security, deprecated =
|
15
|
+
extractor.request_attributes(request, example)
|
16
16
|
|
17
17
|
return if RSpec::OpenAPI.ignored_paths.any? { |ignored_path| path.match?(ignored_path) }
|
18
18
|
|
@@ -32,6 +32,7 @@ class << RSpec::OpenAPI::RecordBuilder = Object.new
|
|
32
32
|
operation_id: operation_id,
|
33
33
|
description: description,
|
34
34
|
security: security,
|
35
|
+
deprecated: deprecated,
|
35
36
|
status: response.status,
|
36
37
|
response_body: safe_parse_body(response, response.media_type),
|
37
38
|
response_headers: response_headers,
|
@@ -54,7 +55,10 @@ class << RSpec::OpenAPI::RecordBuilder = Object.new
|
|
54
55
|
def extract_headers(request, response)
|
55
56
|
request_headers = RSpec::OpenAPI.request_headers.each_with_object([]) do |header, headers_arr|
|
56
57
|
header_key = header.gsub('-', '_').upcase.to_sym
|
57
|
-
|
58
|
+
|
59
|
+
header_value = request.get_header(['HTTP', header_key].join('_')) ||
|
60
|
+
request.get_header(header_key) ||
|
61
|
+
request.get_header(header_key.to_s)
|
58
62
|
headers_arr << [header, header_value] if header_value
|
59
63
|
end
|
60
64
|
response_headers = RSpec::OpenAPI.response_headers.each_with_object([]) do |header, headers_arr|
|
@@ -65,70 +69,6 @@ class << RSpec::OpenAPI::RecordBuilder = Object.new
|
|
65
69
|
[request_headers, response_headers]
|
66
70
|
end
|
67
71
|
|
68
|
-
def extract_request_attributes(request, example)
|
69
|
-
metadata = example.metadata[:openapi] || {}
|
70
|
-
summary = metadata[:summary] || RSpec::OpenAPI.summary_builder.call(example)
|
71
|
-
tags = metadata[:tags] || RSpec::OpenAPI.tags_builder.call(example)
|
72
|
-
operation_id = metadata[:operation_id]
|
73
|
-
required_request_params = metadata[:required_request_params] || []
|
74
|
-
security = metadata[:security]
|
75
|
-
description = metadata[:description] || RSpec::OpenAPI.description_builder.call(example)
|
76
|
-
raw_path_params = request.path_parameters
|
77
|
-
path = request.path
|
78
|
-
if rails?
|
79
|
-
# Reverse the destructive modification by Rails https://github.com/rails/rails/blob/v6.0.3.4/actionpack/lib/action_dispatch/journey/router.rb#L33-L41
|
80
|
-
fixed_request = request.dup
|
81
|
-
fixed_request.path_info = File.join(request.script_name, request.path_info) if request.script_name.present?
|
82
|
-
|
83
|
-
route, path = find_rails_route(fixed_request)
|
84
|
-
raise "No route matched for #{fixed_request.request_method} #{fixed_request.path_info}" if route.nil?
|
85
|
-
|
86
|
-
path = path.delete_suffix('(.:format)')
|
87
|
-
summary ||= route.requirements[:action]
|
88
|
-
tags ||= [route.requirements[:controller]&.classify].compact
|
89
|
-
# :controller and :action always exist. :format is added when routes is configured as such.
|
90
|
-
# TODO: Use .except(:controller, :action, :format) when we drop support for Ruby 2.x
|
91
|
-
raw_path_params = raw_path_params.slice(*(raw_path_params.keys - RSpec::OpenAPI.ignored_path_params))
|
92
|
-
end
|
93
|
-
summary ||= "#{request.method} #{path}"
|
94
|
-
[path, summary, tags, operation_id, required_request_params, raw_path_params, description, security]
|
95
|
-
end
|
96
|
-
|
97
|
-
def extract_request_response(context)
|
98
|
-
if rack_test?(context)
|
99
|
-
request = ActionDispatch::Request.new(context.last_request.env)
|
100
|
-
request.body.rewind if request.body.respond_to?(:rewind)
|
101
|
-
response = ActionDispatch::TestResponse.new(*context.last_response.to_a)
|
102
|
-
else
|
103
|
-
request = context.request
|
104
|
-
response = context.response
|
105
|
-
end
|
106
|
-
[request, response]
|
107
|
-
end
|
108
|
-
|
109
|
-
def rails?
|
110
|
-
defined?(Rails) && Rails.respond_to?(:application) && Rails.application
|
111
|
-
end
|
112
|
-
|
113
|
-
def rack_test?(context)
|
114
|
-
defined?(Rack::Test::Methods) && context.class < Rack::Test::Methods
|
115
|
-
end
|
116
|
-
|
117
|
-
# @param [ActionDispatch::Request] request
|
118
|
-
def find_rails_route(request, app: Rails.application, path_prefix: '')
|
119
|
-
app.routes.router.recognize(request) do |route|
|
120
|
-
path = route.path.spec.to_s
|
121
|
-
if route.app.matches?(request)
|
122
|
-
if route.app.engine?
|
123
|
-
route, path = find_rails_route(request, app: route.app.app, path_prefix: path)
|
124
|
-
next if route.nil?
|
125
|
-
end
|
126
|
-
return [route, path_prefix + path]
|
127
|
-
end
|
128
|
-
end
|
129
|
-
nil
|
130
|
-
end
|
131
|
-
|
132
72
|
# workaround to get real request parameters
|
133
73
|
# because ActionController::ParamsWrapper overwrites request_parameters
|
134
74
|
def raw_request_params(request)
|
@@ -29,11 +29,8 @@ class RSpec::OpenAPI::ResultRecorder
|
|
29
29
|
rescue StandardError, NotImplementedError => e # e.g. SchemaBuilder raises a NotImplementedError
|
30
30
|
@error_records[e] = record # Avoid failing the build
|
31
31
|
end
|
32
|
-
|
33
|
-
|
34
|
-
RSpec::OpenAPI::ComponentsUpdater.update!(spec, new_from_zero)
|
35
|
-
RSpec::OpenAPI::SchemaCleaner.cleanup_empty_required_array!(spec)
|
36
|
-
RSpec::OpenAPI::SchemaSorter.deep_sort!(spec)
|
32
|
+
cleanup_schema!(new_from_zero, spec)
|
33
|
+
execute_post_process_hook(path, records, spec)
|
37
34
|
end
|
38
35
|
end
|
39
36
|
end
|
@@ -49,4 +46,18 @@ class RSpec::OpenAPI::ResultRecorder
|
|
49
46
|
#{@error_records.map { |e, record| "#{e.inspect}: #{record.inspect}" }.join("\n")}
|
50
47
|
ERR_MSG
|
51
48
|
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def execute_post_process_hook(path, records, spec)
|
53
|
+
RSpec::OpenAPI.post_process_hook.call(path, records, spec) if RSpec::OpenAPI.post_process_hook.is_a?(Proc)
|
54
|
+
end
|
55
|
+
|
56
|
+
def cleanup_schema!(new_from_zero, spec)
|
57
|
+
RSpec::OpenAPI::SchemaCleaner.cleanup_conflicting_security_parameters!(spec)
|
58
|
+
RSpec::OpenAPI::SchemaCleaner.cleanup!(spec, new_from_zero)
|
59
|
+
RSpec::OpenAPI::ComponentsUpdater.update!(spec, new_from_zero)
|
60
|
+
RSpec::OpenAPI::SchemaCleaner.cleanup_empty_required_array!(spec)
|
61
|
+
RSpec::OpenAPI::SchemaSorter.deep_sort!(spec)
|
62
|
+
end
|
52
63
|
end
|
@@ -5,7 +5,7 @@ require 'rspec/core'
|
|
5
5
|
RSpec.configuration.after(:each) do |example|
|
6
6
|
if RSpec::OpenAPI.example_types.include?(example.metadata[:type]) && example.metadata[:openapi] != false
|
7
7
|
path = RSpec::OpenAPI.path.then { |p| p.is_a?(Proc) ? p.call(example) : p }
|
8
|
-
record = RSpec::OpenAPI::RecordBuilder.build(self, example: example)
|
8
|
+
record = RSpec::OpenAPI::RecordBuilder.build(self, example: example, extractor: SharedHooks.find_extractor)
|
9
9
|
RSpec::OpenAPI.path_records[path] << record if record
|
10
10
|
end
|
11
11
|
end
|
@@ -34,6 +34,7 @@ class << RSpec::OpenAPI::SchemaBuilder = Object.new
|
|
34
34
|
tags: record.tags,
|
35
35
|
operationId: record.operation_id,
|
36
36
|
security: record.security,
|
37
|
+
deprecated: record.deprecated ? true : nil,
|
37
38
|
parameters: build_parameters(record),
|
38
39
|
requestBody: http_method == 'get' ? nil : build_request_body(record),
|
39
40
|
responses: {
|
@@ -53,7 +53,8 @@ class << RSpec::OpenAPI::SchemaMerger = Object.new
|
|
53
53
|
def merge_parameters(base, key, value)
|
54
54
|
all_parameters = value | base[key]
|
55
55
|
|
56
|
-
unique_base_parameters = base
|
56
|
+
unique_base_parameters = build_unique_params(base, key)
|
57
|
+
|
57
58
|
all_parameters = all_parameters.map do |parameter|
|
58
59
|
base_parameter = unique_base_parameters[[parameter[:name], parameter[:in]]] || {}
|
59
60
|
base_parameter ? base_parameter.merge(parameter) : parameter
|
@@ -63,6 +64,12 @@ class << RSpec::OpenAPI::SchemaMerger = Object.new
|
|
63
64
|
base[key] = all_parameters
|
64
65
|
end
|
65
66
|
|
67
|
+
def build_unique_params(base, key)
|
68
|
+
base[key].each_with_object({}) do |parameter, hash|
|
69
|
+
hash[[parameter[:name], parameter[:in]]] = parameter
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
66
73
|
SIMILARITY_THRESHOLD = 0.5
|
67
74
|
|
68
75
|
def merge_closest_match!(options, spec)
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SharedHooks
|
4
|
+
def self.find_extractor
|
5
|
+
if defined?(Rails) && Rails.respond_to?(:application) && Rails.application
|
6
|
+
RSpec::OpenAPI::Extractors::Rails
|
7
|
+
elsif defined?(Hanami) && Hanami.respond_to?(:app) && Hanami.app?
|
8
|
+
RSpec::OpenAPI::Extractors::Hanami
|
9
|
+
# elsif defined?(Roda)
|
10
|
+
# some Roda extractor
|
11
|
+
else
|
12
|
+
RSpec::OpenAPI::Extractors::Rack
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/rspec/openapi.rb
CHANGED
@@ -11,6 +11,23 @@ require 'rspec/openapi/schema_merger'
|
|
11
11
|
require 'rspec/openapi/schema_cleaner'
|
12
12
|
require 'rspec/openapi/schema_sorter'
|
13
13
|
require 'rspec/openapi/key_transformer'
|
14
|
+
require 'rspec/openapi/shared_hooks'
|
15
|
+
require 'rspec/openapi/extractors'
|
16
|
+
require 'rspec/openapi/extractors/rack'
|
17
|
+
|
18
|
+
begin
|
19
|
+
require 'hanami'
|
20
|
+
require 'rspec/openapi/extractors/hanami'
|
21
|
+
rescue LoadError
|
22
|
+
puts 'Hanami not detected'
|
23
|
+
end
|
24
|
+
|
25
|
+
begin
|
26
|
+
require 'rails'
|
27
|
+
require 'rspec/openapi/extractors/rails'
|
28
|
+
rescue LoadError
|
29
|
+
puts 'Rails not detected'
|
30
|
+
end
|
14
31
|
|
15
32
|
require 'rspec/openapi/minitest_hooks' if Object.const_defined?('Minitest')
|
16
33
|
require 'rspec/openapi/rspec_hooks' if ENV['OPENAPI'] && Object.const_defined?('RSpec')
|
@@ -33,6 +50,7 @@ module RSpec::OpenAPI
|
|
33
50
|
@path_records = Hash.new { |h, k| h[k] = [] }
|
34
51
|
@ignored_path_params = %i[controller action format]
|
35
52
|
@ignored_paths = []
|
53
|
+
@post_process_hook = nil
|
36
54
|
|
37
55
|
# This is the configuraion override file name we look for within each path.
|
38
56
|
@config_filename = 'rspec_openapi.rb'
|
@@ -54,7 +72,8 @@ module RSpec::OpenAPI
|
|
54
72
|
:response_headers,
|
55
73
|
:path_records,
|
56
74
|
:ignored_paths,
|
57
|
-
:ignored_path_params
|
75
|
+
:ignored_path_params,
|
76
|
+
:post_process_hook
|
58
77
|
|
59
78
|
attr_reader :config_filename
|
60
79
|
end
|
data/rspec-openapi.gemspec
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rspec-openapi
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.17.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Takashi Kokubun
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date: 2024-
|
12
|
+
date: 2024-04-12 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: actionpack
|
@@ -25,6 +25,20 @@ dependencies:
|
|
25
25
|
- - ">="
|
26
26
|
- !ruby/object:Gem::Version
|
27
27
|
version: 5.2.0
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: rails-dom-testing
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '0'
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
28
42
|
- !ruby/object:Gem::Dependency
|
29
43
|
name: rspec-core
|
30
44
|
requirement: !ruby/object:Gem::Requirement
|
@@ -66,6 +80,10 @@ files:
|
|
66
80
|
- lib/rspec/openapi.rb
|
67
81
|
- lib/rspec/openapi/components_updater.rb
|
68
82
|
- lib/rspec/openapi/default_schema.rb
|
83
|
+
- lib/rspec/openapi/extractors.rb
|
84
|
+
- lib/rspec/openapi/extractors/hanami.rb
|
85
|
+
- lib/rspec/openapi/extractors/rack.rb
|
86
|
+
- lib/rspec/openapi/extractors/rails.rb
|
69
87
|
- lib/rspec/openapi/hash_helper.rb
|
70
88
|
- lib/rspec/openapi/key_transformer.rb
|
71
89
|
- lib/rspec/openapi/minitest_hooks.rb
|
@@ -78,6 +96,7 @@ files:
|
|
78
96
|
- lib/rspec/openapi/schema_file.rb
|
79
97
|
- lib/rspec/openapi/schema_merger.rb
|
80
98
|
- lib/rspec/openapi/schema_sorter.rb
|
99
|
+
- lib/rspec/openapi/shared_hooks.rb
|
81
100
|
- lib/rspec/openapi/version.rb
|
82
101
|
- rspec-openapi.gemspec
|
83
102
|
- scripts/rspec
|
@@ -89,7 +108,7 @@ licenses:
|
|
89
108
|
metadata:
|
90
109
|
homepage_uri: https://github.com/exoego/rspec-openapi
|
91
110
|
source_code_uri: https://github.com/exoego/rspec-openapi
|
92
|
-
changelog_uri: https://github.com/exoego/rspec-openapi/releases/tag/v0.
|
111
|
+
changelog_uri: https://github.com/exoego/rspec-openapi/releases/tag/v0.17.0
|
93
112
|
rubygems_mfa_required: 'true'
|
94
113
|
post_install_message:
|
95
114
|
rdoc_options: []
|