rspec-openapi 0.11.0 → 0.13.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/.github/workflows/test.yml +4 -1
- data/CHANGELOG.md +11 -2
- data/lib/rspec/openapi/components_updater.rb +25 -5
- data/lib/rspec/openapi/hash_helper.rb +4 -0
- data/lib/rspec/openapi/minitest_hooks.rb +1 -1
- data/lib/rspec/openapi/record_builder.rb +1 -1
- data/lib/rspec/openapi/schema_merger.rb +43 -1
- data/lib/rspec/openapi/version.rb +1 -1
- data/rspec-openapi.gemspec +6 -3
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a5139294ebac456aadd2c09832f55cb214b7bf4ba9893919445823455cbee18a
|
4
|
+
data.tar.gz: b77813ff2d18290e714482bfa45350f69b182a638162d083923acb9b21124cfe
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 58de63dbf9608beb5f313eb57e6e1e4386eefa23fd1b572d17b06319f571ca3c6b1ac172a7886b3dcd42e14feb1f5f60ebd5a532951441193f8400d66e5229ce
|
7
|
+
data.tar.gz: 95765054acd231993f058a9f3a31a596c56ff143af9f1dc576bfb2f201773e611025329069b8c49751f50d3bf14b16a4f83ef863587d2f7815209f0afcdccb4a
|
data/.github/workflows/test.yml
CHANGED
@@ -43,9 +43,12 @@ jobs:
|
|
43
43
|
if: matrix.coverage == 'coverage'
|
44
44
|
- run: bundle exec rspec
|
45
45
|
timeout-minutes: 1
|
46
|
+
- run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
47
|
+
name: codecov-action@v4 workaround
|
46
48
|
- name: Upload coverage reports
|
47
|
-
uses: codecov/codecov-action@
|
49
|
+
uses: codecov/codecov-action@v4
|
48
50
|
if: matrix.coverage == 'coverage'
|
49
51
|
with:
|
50
52
|
fail_ci_if_error: true
|
51
53
|
files: ./coverage/coverage.xml
|
54
|
+
token: ${{ secrets.CODECOV_TOKEN }}
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,15 @@
|
|
1
|
-
|
1
|
+
# THIS CHANGELOG IS DEPRECATED!!
|
2
|
+
|
3
|
+
Refer https://github.com/exoego/rspec-openapi/releases instead.
|
4
|
+
|
5
|
+
## v0.12.0
|
2
6
|
|
3
|
-
|
7
|
+
- feat: Initial support of complex schema with manually-added `oneOf`
|
8
|
+
[#174](https://github.com/exoego/rspec-openapi/pull/174)
|
9
|
+
- chore: Test with Ruby 3.3 and Rails 7.1.x
|
10
|
+
[#169](https://github.com/exoego/rspec-openapi/pull/169)
|
11
|
+
|
12
|
+
## v0.11.0
|
4
13
|
- feat: Allow path-based config overrides
|
5
14
|
[#162](https://github.com/exoego/rspec-openapi/pull/162)
|
6
15
|
- enhancement: Sort HTTP methods, response status codes, and contents lexicographically
|
@@ -23,7 +23,8 @@ class << RSpec::OpenAPI::ComponentsUpdater = Object.new
|
|
23
23
|
# 0 1 2 ^...............................^
|
24
24
|
# ["components", "schema", "Table", "properties", "owner", "properties", "company", "$ref"]
|
25
25
|
# 0 1 2 ^...........................................^
|
26
|
-
needle = paths.
|
26
|
+
needle = paths.reject { |path| path.is_a?(Integer) || path == 'oneOf' }
|
27
|
+
needle = needle.slice(2, needle.size - 3)
|
27
28
|
nested_schema = fresh_schemas.dig(*needle)
|
28
29
|
|
29
30
|
# Skip if the property using $ref is not found in the parent schema. The property may be removed.
|
@@ -44,20 +45,28 @@ class << RSpec::OpenAPI::ComponentsUpdater = Object.new
|
|
44
45
|
references.inject({}) do |acc, paths|
|
45
46
|
ref_link = dig_schema(base, paths)['$ref']
|
46
47
|
schema_name = ref_link.gsub('#/components/schemas/', '')
|
47
|
-
schema_body = dig_schema(fresh, paths)
|
48
|
+
schema_body = dig_schema(fresh, paths.reject { |path| path.is_a?(Integer) })
|
49
|
+
|
48
50
|
RSpec::OpenAPI::SchemaMerger.merge!(acc, { schema_name => schema_body })
|
49
51
|
end
|
50
52
|
end
|
51
53
|
|
52
54
|
def dig_schema(obj, paths)
|
53
|
-
obj.dig(*paths, 'schema', 'items')
|
55
|
+
item_schema = obj.dig(*paths, 'schema', 'items')
|
56
|
+
object_schema = obj.dig(*paths, 'schema')
|
57
|
+
one_of_schema = obj.dig(*paths.take(paths.size - 1), 'schema', 'oneOf', paths.last)
|
58
|
+
|
59
|
+
item_schema || object_schema || one_of_schema
|
54
60
|
end
|
55
61
|
|
56
62
|
def paths_to_top_level_refs(base)
|
57
63
|
request_bodies = RSpec::OpenAPI::HashHelper.matched_paths(base, 'paths.*.*.requestBody.content.application/json')
|
58
64
|
responses = RSpec::OpenAPI::HashHelper.matched_paths(base, 'paths.*.*.responses.*.content.application/json')
|
59
|
-
(request_bodies + responses).
|
60
|
-
|
65
|
+
(request_bodies + responses).flat_map do |paths|
|
66
|
+
object_paths = find_object_refs(base, paths)
|
67
|
+
one_of_paths = find_one_of_refs(base, paths)
|
68
|
+
|
69
|
+
object_paths || one_of_paths || []
|
61
70
|
end
|
62
71
|
end
|
63
72
|
|
@@ -65,6 +74,7 @@ class << RSpec::OpenAPI::ComponentsUpdater = Object.new
|
|
65
74
|
nested_refs = [
|
66
75
|
*RSpec::OpenAPI::HashHelper.matched_paths_deeply_nested(base, 'components.schemas', 'properties.*.$ref'),
|
67
76
|
*RSpec::OpenAPI::HashHelper.matched_paths_deeply_nested(base, 'components.schemas', 'properties.*.items.$ref'),
|
77
|
+
*RSpec::OpenAPI::HashHelper.matched_paths_deeply_nested(base, 'components.schemas', 'oneOf.*.$ref'),
|
68
78
|
]
|
69
79
|
# Reject already-generated schemas to reduce unnecessary loop
|
70
80
|
nested_refs.reject do |paths|
|
@@ -73,4 +83,14 @@ class << RSpec::OpenAPI::ComponentsUpdater = Object.new
|
|
73
83
|
generated_names.include?(schema_name)
|
74
84
|
end
|
75
85
|
end
|
86
|
+
|
87
|
+
def find_one_of_refs(base, paths)
|
88
|
+
dig_schema(base, paths)&.dig('oneOf')&.map&.with_index do |schema, index|
|
89
|
+
paths + [index] if schema&.dig('$ref')&.start_with?('#/components/schemas/')
|
90
|
+
end&.compact
|
91
|
+
end
|
92
|
+
|
93
|
+
def find_object_refs(base, paths)
|
94
|
+
[paths] if dig_schema(base, paths)&.dig('$ref')&.start_with?('#/components/schemas/')
|
95
|
+
end
|
76
96
|
end
|
@@ -8,6 +8,10 @@ class << RSpec::OpenAPI::HashHelper = Object.new
|
|
8
8
|
k = k.to_s
|
9
9
|
[[k]] + paths_to_all_fields(v).map { |x| [k, *x] }
|
10
10
|
end
|
11
|
+
when Array
|
12
|
+
obj.flat_map.with_index do |value, i|
|
13
|
+
[[i]] + paths_to_all_fields(value).map { |x| [i, *x] }
|
14
|
+
end
|
11
15
|
else
|
12
16
|
[]
|
13
17
|
end
|
@@ -10,7 +10,7 @@ module RSpec::OpenAPI::Minitest
|
|
10
10
|
result = super
|
11
11
|
if ENV['OPENAPI'] && self.class.openapi?
|
12
12
|
file_path = method(name).source_location.first
|
13
|
-
human_name = name.sub(/^test_/, '').gsub(
|
13
|
+
human_name = name.sub(/^test_/, '').gsub('_', ' ')
|
14
14
|
example = Example.new(self, human_name, {}, file_path)
|
15
15
|
path = RSpec::OpenAPI.path.yield_self { |p| p.is_a?(Proc) ? p.call(example) : p }
|
16
16
|
record = RSpec::OpenAPI::RecordBuilder.build(self, example: example)
|
@@ -48,7 +48,7 @@ class << RSpec::OpenAPI::RecordBuilder = Object.new
|
|
48
48
|
|
49
49
|
def extract_headers(request, response)
|
50
50
|
request_headers = RSpec::OpenAPI.request_headers.each_with_object([]) do |header, headers_arr|
|
51
|
-
header_key = header.gsub(
|
51
|
+
header_key = header.gsub('-', '_').upcase
|
52
52
|
header_value = request.get_header(['HTTP', header_key].join('_')) || request.get_header(header_key)
|
53
53
|
headers_arr << [header, header_value] if header_value
|
54
54
|
end
|
@@ -29,6 +29,12 @@ class << RSpec::OpenAPI::SchemaMerger = Object.new
|
|
29
29
|
#
|
30
30
|
# TODO: Should we probably force-merge `summary` regardless of manual modifications?
|
31
31
|
def merge_schema!(base, spec)
|
32
|
+
if (options = base['oneOf'])
|
33
|
+
merge_closest_match!(options, spec)
|
34
|
+
|
35
|
+
return base
|
36
|
+
end
|
37
|
+
|
32
38
|
spec.each do |key, value|
|
33
39
|
if base[key].is_a?(Hash) && value.is_a?(Hash)
|
34
40
|
merge_schema!(base[key], value) unless base[key].key?('$ref')
|
@@ -36,7 +42,8 @@ class << RSpec::OpenAPI::SchemaMerger = Object.new
|
|
36
42
|
# parameters need to be merged as if `name` and `in` were the Hash keys.
|
37
43
|
merge_arrays(base, key, value)
|
38
44
|
else
|
39
|
-
|
45
|
+
# do not ADD `properties` or `required` fields if `additionalProperties` field is present
|
46
|
+
base[key] = value unless base.key?('additionalProperties') && %w[properties required].include?(key)
|
40
47
|
end
|
41
48
|
end
|
42
49
|
base
|
@@ -67,4 +74,39 @@ class << RSpec::OpenAPI::SchemaMerger = Object.new
|
|
67
74
|
all_parameters.uniq! { |param| param.slice('name', 'in') }
|
68
75
|
base[key] = all_parameters
|
69
76
|
end
|
77
|
+
|
78
|
+
SIMILARITY_THRESHOLD = 0.5
|
79
|
+
|
80
|
+
def merge_closest_match!(options, spec)
|
81
|
+
score, option = options.map { |option| [similarity(option, spec), option] }.max_by(&:first)
|
82
|
+
|
83
|
+
return if option&.key?('$ref')
|
84
|
+
|
85
|
+
if score.to_f > SIMILARITY_THRESHOLD
|
86
|
+
merge_schema!(option, spec)
|
87
|
+
else
|
88
|
+
options.push(spec)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def similarity(first, second)
|
93
|
+
return 1 if first == second
|
94
|
+
|
95
|
+
score =
|
96
|
+
case [first.class, second.class]
|
97
|
+
when [Array, Array]
|
98
|
+
(first & second).size / [first.size, second.size].max.to_f
|
99
|
+
when [Hash, Hash]
|
100
|
+
return 1 if first.merge(second).key?('$ref')
|
101
|
+
|
102
|
+
intersection = first.keys & second.keys
|
103
|
+
total_size = [first.size, second.size].max.to_f
|
104
|
+
|
105
|
+
intersection.sum { |key| similarity(first[key], second[key]) } / total_size
|
106
|
+
else
|
107
|
+
0
|
108
|
+
end
|
109
|
+
|
110
|
+
score.finite? ? score : 0
|
111
|
+
end
|
70
112
|
end
|
data/rspec-openapi.gemspec
CHANGED
@@ -14,9 +14,12 @@ Gem::Specification.new do |spec|
|
|
14
14
|
spec.license = 'MIT'
|
15
15
|
spec.required_ruby_version = Gem::Requirement.new('>= 2.5.0')
|
16
16
|
|
17
|
-
spec.metadata
|
18
|
-
|
19
|
-
|
17
|
+
spec.metadata = {
|
18
|
+
'homepage_uri' => 'https://github.com/exoego/rspec-openapi',
|
19
|
+
'source_code_uri' => 'https://github.com/exoego/rspec-openapi',
|
20
|
+
'changelog_uri' => "https://github.com/exoego/rspec-openapi/releases/tag/v#{RSpec::OpenAPI::VERSION}",
|
21
|
+
'rubygems_mfa_required' => 'true',
|
22
|
+
}
|
20
23
|
|
21
24
|
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
22
25
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
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.13.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-03-02 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: actionpack
|
@@ -88,7 +88,7 @@ licenses:
|
|
88
88
|
metadata:
|
89
89
|
homepage_uri: https://github.com/exoego/rspec-openapi
|
90
90
|
source_code_uri: https://github.com/exoego/rspec-openapi
|
91
|
-
changelog_uri: https://github.com/exoego/rspec-openapi/
|
91
|
+
changelog_uri: https://github.com/exoego/rspec-openapi/releases/tag/v0.13.0
|
92
92
|
rubygems_mfa_required: 'true'
|
93
93
|
post_install_message:
|
94
94
|
rdoc_options: []
|