skooma 0.3.4 → 0.3.5
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/CHANGELOG.md +22 -1
- data/README.md +3 -0
- data/lib/skooma/coverage.rb +20 -5
- data/lib/skooma/coverage_store.rb +69 -0
- data/lib/skooma/keywords/oas_3_1/dialect/additional_properties.rb +1 -1
- data/lib/skooma/matchers/wrapper.rb +7 -2
- data/lib/skooma/minitest.rb +3 -1
- data/lib/skooma/objects/openapi/keywords/paths.rb +76 -10
- data/lib/skooma/objects/openapi.rb +10 -0
- data/lib/skooma/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ecc89f4b7b9744168f5847936e80a6e43d1d9ab32e31df584adaf5c85eaa27af
|
4
|
+
data.tar.gz: bc186b5d374ea4fd8703b8bb58c1dc921760e6731de266e7914035868c921b47
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 45660ab6ee53e2be9822ee9fccda2850719f10d230a120e4cf1b72855fb88e0cf263f72612e8648b0dccced1cfc08e5e08a4cc462c7f7f8b50fe5122a42a49a3
|
7
|
+
data.tar.gz: 798b59438bdd1ccf3cea2633372f581118dc979fc6c0347b5c0c98b070c4b8a3e765343606cdd756b4b3133d212cf3e2bc06a06bee23273f9a4f4687f1146f5e
|
data/CHANGELOG.md
CHANGED
@@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning].
|
|
7
7
|
|
8
8
|
## [Unreleased]
|
9
9
|
|
10
|
+
## [0.3.5] - 2025-07-31
|
11
|
+
|
12
|
+
### Fixed
|
13
|
+
|
14
|
+
- Fix the Enforce Access mode with additional properties. ([@aburgel])
|
15
|
+
- Introduce coverage storage to fix Minitest parallel workers reports. ([@skarlcf])
|
16
|
+
- Introduce `use_patterns_for_path_matching` option to allow using `path` patterns for path matching. ([@jandouwebeekman])
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
# spec/rails_helper.rb
|
20
|
+
|
21
|
+
RSpec.configure do |config|
|
22
|
+
# To enable path patterns, pass `use_patterns_for_path_matching: true` option:
|
23
|
+
config.include Skooma::RSpec[Rails.root.join("docs", "openapi.yml"), use_patterns_for_path_matching: true], type: :request
|
24
|
+
end
|
25
|
+
```
|
26
|
+
|
10
27
|
## [0.3.4] - 2025-01-14
|
11
28
|
|
12
29
|
### Added
|
@@ -140,12 +157,16 @@ and this project adheres to [Semantic Versioning].
|
|
140
157
|
|
141
158
|
- Initial implementation. ([@skryukov])
|
142
159
|
|
160
|
+
[@aburgel]: https://github.com/aburgel
|
143
161
|
[@barnaclebarnes]: https://github.com/barnaclebarnes
|
162
|
+
[@jandouwebeekman]: https://github.com/jandouwebeekman
|
144
163
|
[@pvcarrera]: https://github.com/pvcarrera
|
164
|
+
[@skarlcf]: https://github.com/skarlcf
|
145
165
|
[@skryukov]: https://github.com/skryukov
|
146
166
|
[@ursm]: https://github.com/ursm
|
147
167
|
|
148
|
-
[Unreleased]: https://github.com/skryukov/skooma/compare/v0.3.
|
168
|
+
[Unreleased]: https://github.com/skryukov/skooma/compare/v0.3.5...HEAD
|
169
|
+
[0.3.5]: https://github.com/skryukov/skooma/compare/v0.3.4...v0.3.5
|
149
170
|
[0.3.4]: https://github.com/skryukov/skooma/compare/v0.3.3...v0.3.4
|
150
171
|
[0.3.3]: https://github.com/skryukov/skooma/compare/v0.3.2...v0.3.3
|
151
172
|
[0.3.2]: https://github.com/skryukov/skooma/compare/v0.3.1...v0.3.2
|
data/README.md
CHANGED
@@ -132,6 +132,9 @@ ActionDispatch::IntegrationTest.include Skooma::Minitest[path_to_openapi, covera
|
|
132
132
|
# EXPERIMENTAL
|
133
133
|
# To enable support for readOnly and writeOnly keywords, pass `enforce_access_modes: true` option:
|
134
134
|
ActionDispatch::IntegrationTest.include Skooma::Minitest[path_to_openapi, enforce_access_modes: true], type: :request
|
135
|
+
|
136
|
+
# To enable custom regex patterns for path parameters, pass `use_patterns_for_path_matching: true` option.
|
137
|
+
ActionDispatch::IntegrationTest.include Skooma::Minitest[path_to_openapi, use_patterns_for_path_matching: true], type: :request
|
135
138
|
```
|
136
139
|
|
137
140
|
#### Validate OpenAPI document
|
data/lib/skooma/coverage.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Skooma
|
2
4
|
class NoopCoverage
|
3
5
|
def track_request(*)
|
@@ -24,7 +26,7 @@ module Skooma
|
|
24
26
|
end
|
25
27
|
end
|
26
28
|
|
27
|
-
def self.new(schema, mode: nil, format: nil)
|
29
|
+
def self.new(schema, mode: nil, format: nil, storage: nil)
|
28
30
|
case mode
|
29
31
|
when nil, false
|
30
32
|
NoopCoverage.new
|
@@ -35,14 +37,23 @@ module Skooma
|
|
35
37
|
end
|
36
38
|
end
|
37
39
|
|
38
|
-
attr_reader :mode, :format, :defined_paths, :
|
40
|
+
attr_reader :mode, :format, :defined_paths, :storage, :schema
|
41
|
+
attr_accessor :covered_paths
|
39
42
|
|
40
|
-
def initialize(schema, mode:, format:)
|
43
|
+
def initialize(schema, mode:, format:, storage:)
|
41
44
|
@schema = schema
|
42
45
|
@mode = mode
|
43
46
|
@format = format || SimpleReport
|
44
|
-
@
|
45
|
-
|
47
|
+
@storage = storage || CoverageStore.new
|
48
|
+
|
49
|
+
stored_data = @storage.load_data
|
50
|
+
@defined_paths = stored_data[:defined_paths]
|
51
|
+
@covered_paths = stored_data[:covered_paths]
|
52
|
+
|
53
|
+
if @defined_paths.empty?
|
54
|
+
@defined_paths = find_defined_paths(schema)
|
55
|
+
@storage.save_data(@defined_paths, @covered_paths)
|
56
|
+
end
|
46
57
|
end
|
47
58
|
|
48
59
|
def track_request(result)
|
@@ -57,6 +68,7 @@ module Skooma
|
|
57
68
|
end
|
58
69
|
end
|
59
70
|
covered_paths << operation
|
71
|
+
storage.save_data(Set.new, Set.new([operation]))
|
60
72
|
end
|
61
73
|
|
62
74
|
def uncovered_paths
|
@@ -68,7 +80,10 @@ module Skooma
|
|
68
80
|
end
|
69
81
|
|
70
82
|
def report
|
83
|
+
stored_data = storage.load_data
|
84
|
+
self.covered_paths = stored_data[:covered_paths]
|
71
85
|
format.new(self).report
|
86
|
+
storage.clear
|
72
87
|
exit 1 if mode == :strict && uncovered_paths.any?
|
73
88
|
end
|
74
89
|
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Skooma
|
4
|
+
class CoverageStore
|
5
|
+
DEFAULT_FILE_PATH = File.join(Dir.pwd, "tmp", "skooma_coverage.json")
|
6
|
+
|
7
|
+
attr_reader :file_path
|
8
|
+
|
9
|
+
def initialize(file_path: DEFAULT_FILE_PATH)
|
10
|
+
@file_path = file_path
|
11
|
+
ensure_file_exists
|
12
|
+
end
|
13
|
+
|
14
|
+
def load_data
|
15
|
+
with_lock("r") do |file|
|
16
|
+
parse_data(file.read)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def save_data(new_defined_paths, new_covered_paths)
|
21
|
+
with_lock("r+") do |file|
|
22
|
+
existing_data = parse_data(file.read)
|
23
|
+
merged_data = merge_data(existing_data, new_defined_paths, new_covered_paths)
|
24
|
+
|
25
|
+
file.rewind
|
26
|
+
file.write(JSON.generate(merged_data))
|
27
|
+
file.flush
|
28
|
+
file.truncate(file.pos)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def clear
|
33
|
+
File.delete(file_path) if File.exist?(file_path)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def ensure_file_exists
|
39
|
+
FileUtils.mkdir_p(File.dirname(@file_path))
|
40
|
+
FileUtils.touch(@file_path) unless File.exist?(@file_path)
|
41
|
+
end
|
42
|
+
|
43
|
+
def parse_data(content)
|
44
|
+
return {defined_paths: Set.new, covered_paths: Set.new} if content.strip.empty?
|
45
|
+
|
46
|
+
data = JSON.parse(content, symbolize_names: true)
|
47
|
+
{
|
48
|
+
defined_paths: Set.new(data[:defined_paths]),
|
49
|
+
covered_paths: Set.new(data[:covered_paths])
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
53
|
+
def merge_data(existing_data, new_defined_paths, new_covered_paths)
|
54
|
+
{
|
55
|
+
defined_paths: (existing_data[:defined_paths] | new_defined_paths).to_a,
|
56
|
+
covered_paths: (existing_data[:covered_paths] | new_covered_paths).to_a
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
def with_lock(mode)
|
61
|
+
File.open(@file_path, mode) do |file|
|
62
|
+
file.flock(File::LOCK_EX)
|
63
|
+
yield(file)
|
64
|
+
ensure
|
65
|
+
file.flock(File::LOCK_UN)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -22,7 +22,7 @@ module Skooma
|
|
22
22
|
properties_result = result.sibling(instance, "properties")
|
23
23
|
instance.each_key do |name|
|
24
24
|
res = properties_result&.children&.[](instance[name]&.path)&.[]name
|
25
|
-
forbidden << name.tap { puts "adding #{name}" } if annotation_exists?(res, key: only_key)
|
25
|
+
forbidden << name.tap { puts "adding #{name}" } if res && annotation_exists?(res, key: only_key)
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
@@ -44,7 +44,7 @@ module Skooma
|
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
47
|
-
def initialize(helper_methods_module, openapi_path, base_uri: "https://skoomarb.dev/", path_prefix: "", enforce_access_modes: false, **params)
|
47
|
+
def initialize(helper_methods_module, openapi_path, base_uri: "https://skoomarb.dev/", path_prefix: "", enforce_access_modes: false, use_patterns_for_path_matching: false, **params)
|
48
48
|
super()
|
49
49
|
|
50
50
|
registry = create_test_registry
|
@@ -59,7 +59,12 @@ module Skooma
|
|
59
59
|
@schema.path_prefix = path_prefix
|
60
60
|
@schema.enforce_access_modes = enforce_access_modes
|
61
61
|
|
62
|
-
|
62
|
+
storage = Skooma::CoverageStore.new(
|
63
|
+
file_path: File.join(Dir.pwd, "tmp", "skooma_coverage_#{Digest::SHA256.hexdigest(source_uri)[0..8]}.json")
|
64
|
+
)
|
65
|
+
@coverage = Coverage.new(@schema, mode: params[:coverage], format: params[:coverage_format], storage: storage)
|
66
|
+
|
67
|
+
@schema.use_patterns_for_path_matching = use_patterns_for_path_matching
|
63
68
|
|
64
69
|
include DefaultHelperMethods
|
65
70
|
include helper_methods_module
|
data/lib/skooma/minitest.rb
CHANGED
@@ -13,14 +13,6 @@ module Skooma
|
|
13
13
|
|
14
14
|
def initialize(parent_schema, value)
|
15
15
|
super
|
16
|
-
@regexp_map = json.filter_map do |path, subschema|
|
17
|
-
next unless path.include?("{") && path.include?("}")
|
18
|
-
|
19
|
-
path_regex = path.gsub(ROUTE_REGEXP, "(?<\\1>[^/?#]+)")
|
20
|
-
path_regex = Regexp.new("\\A#{path_regex}\\z")
|
21
|
-
|
22
|
-
[path, path_regex, subschema]
|
23
|
-
end
|
24
16
|
end
|
25
17
|
|
26
18
|
def evaluate(instance, result)
|
@@ -44,11 +36,34 @@ module Skooma
|
|
44
36
|
|
45
37
|
private
|
46
38
|
|
39
|
+
def regexp_map
|
40
|
+
@regexp_map ||= json.filter_map do |path, subschema|
|
41
|
+
next unless path.include?("{") && path.include?("}")
|
42
|
+
|
43
|
+
pattern_hash = if json.root.use_patterns_for_path_matching?
|
44
|
+
create_hash_of_patterns(subschema)
|
45
|
+
else
|
46
|
+
{}
|
47
|
+
end
|
48
|
+
|
49
|
+
path_regex = path.gsub(ROUTE_REGEXP) do |match|
|
50
|
+
param = match[1..-2]
|
51
|
+
if pattern_hash.key?(param)
|
52
|
+
"(?<#{param}>#{pattern_hash[param]})"
|
53
|
+
else
|
54
|
+
"(?<#{param}>[^/?#]+)"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
path_regex = Regexp.new("\\A#{path_regex}\\z")
|
58
|
+
|
59
|
+
[path, path_regex, subschema]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
47
63
|
def find_route(instance_path)
|
48
64
|
instance_path = instance_path.delete_prefix(json.root.path_prefix)
|
49
65
|
return [instance_path, {}, json[instance_path]] if json.key?(instance_path)
|
50
|
-
|
51
|
-
@regexp_map.reduce(nil) do |result, (path, path_regex, subschema)|
|
66
|
+
regexp_map.reduce(nil) do |result, (path, path_regex, subschema)|
|
52
67
|
next result unless path.include?("{") && path.include?("}")
|
53
68
|
|
54
69
|
match = instance_path.match(path_regex)
|
@@ -58,6 +73,57 @@ module Skooma
|
|
58
73
|
[path, match.named_captures, subschema]
|
59
74
|
end
|
60
75
|
end
|
76
|
+
|
77
|
+
def get_child(parent, child_name)
|
78
|
+
if parent
|
79
|
+
parent_to_use = if parent.key?("$ref")
|
80
|
+
parent.resolve_ref(parent["$ref"])
|
81
|
+
else
|
82
|
+
parent
|
83
|
+
end
|
84
|
+
if parent_to_use.key?(child_name)
|
85
|
+
parent_to_use[child_name]
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def create_hash_of_patterns(subschema)
|
91
|
+
output = {}
|
92
|
+
parameters = []
|
93
|
+
parameters = parameters.concat(subschema["parameters"]) if subschema["parameters"]
|
94
|
+
%w[get post put patch delete].each do |method|
|
95
|
+
parameters = parameters.concat(subschema[method]["parameters"]) if subschema[method] && subschema[method]["parameters"]
|
96
|
+
end
|
97
|
+
parameters.each do |parameter|
|
98
|
+
if get_child(parameter, "in") == "path"
|
99
|
+
pattern = "[^/?#]+"
|
100
|
+
new_pattern = get_child(parameter, "pattern")
|
101
|
+
pattern = new_pattern if new_pattern
|
102
|
+
new_pattern = get_child(get_child(parameter, "schema"), "pattern")
|
103
|
+
pattern = new_pattern if new_pattern
|
104
|
+
|
105
|
+
output[get_child(parameter, "name").to_s] = filter_pattern(pattern)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
output
|
109
|
+
end
|
110
|
+
|
111
|
+
def filter_pattern(pattern)
|
112
|
+
to_return = pattern.to_s
|
113
|
+
if to_return.start_with?("^")
|
114
|
+
to_return = to_return[1..]
|
115
|
+
end
|
116
|
+
if to_return.start_with?('\A')
|
117
|
+
to_return = to_return[2..]
|
118
|
+
end
|
119
|
+
if to_return.end_with?("$")
|
120
|
+
to_return = to_return[0..-2]
|
121
|
+
end
|
122
|
+
if to_return.end_with?('\Z', '\z')
|
123
|
+
to_return = to_return[0..-3]
|
124
|
+
end
|
125
|
+
to_return
|
126
|
+
end
|
61
127
|
end
|
62
128
|
end
|
63
129
|
end
|
@@ -49,6 +49,16 @@ module Skooma
|
|
49
49
|
@enforce_access_modes
|
50
50
|
end
|
51
51
|
|
52
|
+
def use_patterns_for_path_matching=(value)
|
53
|
+
raise ArgumentError, "Use patterns for path matching must be a boolean" unless [true, false].include?(value)
|
54
|
+
|
55
|
+
@use_patterns_for_path_matching = value
|
56
|
+
end
|
57
|
+
|
58
|
+
def use_patterns_for_path_matching?
|
59
|
+
@use_patterns_for_path_matching
|
60
|
+
end
|
61
|
+
|
52
62
|
def path_prefix
|
53
63
|
@path_prefix || ""
|
54
64
|
end
|
data/lib/skooma/version.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: skooma
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Svyatoslav Kryukov
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: zeitwerk
|
@@ -54,6 +54,7 @@ files:
|
|
54
54
|
- lib/skooma.rb
|
55
55
|
- lib/skooma/body_parsers.rb
|
56
56
|
- lib/skooma/coverage.rb
|
57
|
+
- lib/skooma/coverage_store.rb
|
57
58
|
- lib/skooma/dialects/oas_3_1.rb
|
58
59
|
- lib/skooma/env_mapper.rb
|
59
60
|
- lib/skooma/inflector.rb
|
@@ -165,7 +166,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
165
166
|
- !ruby/object:Gem::Version
|
166
167
|
version: '0'
|
167
168
|
requirements: []
|
168
|
-
rubygems_version: 3.6.
|
169
|
+
rubygems_version: 3.6.7
|
169
170
|
specification_version: 4
|
170
171
|
summary: Validate API implementations against OpenAPI documents.
|
171
172
|
test_files: []
|