openapi_first 2.4.0 → 2.5.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/CHANGELOG.md +16 -0
- data/README.md +2 -1
- data/lib/openapi_first/test/coverage/plan.rb +16 -13
- data/lib/openapi_first/test/coverage/request_task.rb +3 -1
- data/lib/openapi_first/test/coverage/response_task.rb +3 -1
- data/lib/openapi_first/test/coverage/terminal_formatter.rb +2 -2
- data/lib/openapi_first/test/coverage.rb +19 -12
- data/lib/openapi_first/test.rb +18 -3
- data/lib/openapi_first/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: be641d2241f6650f7eb0c9bc4a5d78552f2945ee7a030e6003fa0567cb547c85
|
4
|
+
data.tar.gz: 3a7d5911b1c8b30074068a299f8841b791b447240141ca3303814a72bb7424ec
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 65b5e1196c0f9900a8444a7282da2f3ebda189605dc5da0b2bcba3ae96cc5e74acc4550ff57d2aebcea0e41b299c13ffa7641268317ca285997626316a785776
|
7
|
+
data.tar.gz: 43f9452d4b33c0b2a0d1211a629bbbc2c1081b5c23c96d878d73eca2561ade654af0c2040ce992527eba6853acee230551f3a2c0bade8b5a2d142a075cbd10fb
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,22 @@
|
|
2
2
|
|
3
3
|
## Unreleased
|
4
4
|
|
5
|
+
## 2.5.0
|
6
|
+
|
7
|
+
### New feature
|
8
|
+
- Add option to skip certain responses in coverage calculation
|
9
|
+
```ruby
|
10
|
+
require 'openapi_first'
|
11
|
+
OpenapiFirst::Test.setup do |s|
|
12
|
+
test.register('openapi/openapi.yaml')
|
13
|
+
test.skip_response_coverage { it.status == '401' }
|
14
|
+
end
|
15
|
+
```
|
16
|
+
|
17
|
+
### Minor changes
|
18
|
+
- OpenapiFirst::Test.report_coverage now includes fractional digits when returning a coverage value to avoid reporting "0% / no requests made" even though some requests have been made.
|
19
|
+
- Show details about invalid requests / responses in coverage report
|
20
|
+
|
5
21
|
## 2.4.0
|
6
22
|
|
7
23
|
- Support less verbose test setup without the need to call `OpenapiFirst::Test.report_coverage`, which will be called `at_exit`:
|
data/README.md
CHANGED
@@ -149,9 +149,10 @@ Here is how to set it up for RSpec in your `spec/spec_helper.rb`:
|
|
149
149
|
1. Register all OpenAPI documents to track coverage for and start tracking. This should go at the top of you test helper file before loading application code.
|
150
150
|
```ruby
|
151
151
|
require 'openapi_first'
|
152
|
-
OpenapiFirst::Test.setup do |
|
152
|
+
OpenapiFirst::Test.setup do |s|
|
153
153
|
test.register('openapi/openapi.yaml')
|
154
154
|
test.minimum_coverage = 100 # Setting this will lead to an `exit 2` if coverage is below minimum
|
155
|
+
test.skip_response_coverage { it.status == '500' }
|
155
156
|
end
|
156
157
|
```
|
157
158
|
2. Wrap your app with silent request / response validation. This validates all requets/responses you do during your test run. (✷1)
|
@@ -12,20 +12,25 @@ module OpenapiFirst
|
|
12
12
|
class Plan
|
13
13
|
class UnknownRequestError < StandardError; end
|
14
14
|
|
15
|
-
def
|
16
|
-
|
17
|
-
@routes = []
|
18
|
-
@index = {}
|
19
|
-
@filepath = oad.filepath
|
15
|
+
def self.for(oad, skip_response: nil)
|
16
|
+
plan = new(filepath: oad.filepath)
|
20
17
|
oad.routes.each do |route|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
18
|
+
responses = skip_response ? route.responses.reject(&skip_response) : route.responses
|
19
|
+
plan.add_route request_method: route.request_method,
|
20
|
+
path: route.path,
|
21
|
+
requests: route.requests,
|
22
|
+
responses:
|
25
23
|
end
|
24
|
+
plan
|
26
25
|
end
|
27
26
|
|
28
|
-
|
27
|
+
def initialize(filepath:)
|
28
|
+
@routes = []
|
29
|
+
@index = {}
|
30
|
+
@filepath = filepath
|
31
|
+
end
|
32
|
+
|
33
|
+
attr_reader :filepath, :routes
|
29
34
|
private attr_reader :index
|
30
35
|
|
31
36
|
def track_request(validated_request)
|
@@ -45,15 +50,13 @@ module OpenapiFirst
|
|
45
50
|
return 0 if done.zero?
|
46
51
|
|
47
52
|
all = tasks.count
|
48
|
-
(done / (all.to_f / 100))
|
53
|
+
(done / (all.to_f / 100))
|
49
54
|
end
|
50
55
|
|
51
56
|
def tasks
|
52
57
|
index.values
|
53
58
|
end
|
54
59
|
|
55
|
-
private
|
56
|
-
|
57
60
|
def add_route(request_method:, path:, requests:, responses:)
|
58
61
|
request_tasks = requests.to_a.map do |request|
|
59
62
|
index[request.key] = RequestTask.new(request)
|
@@ -14,13 +14,15 @@ module OpenapiFirst
|
|
14
14
|
def initialize(request_definition)
|
15
15
|
@request = request_definition
|
16
16
|
@requested = false
|
17
|
+
@last_error_message = nil
|
17
18
|
end
|
18
19
|
|
19
|
-
attr_reader :request
|
20
|
+
attr_reader :request, :last_error_message
|
20
21
|
|
21
22
|
def track(validated_request)
|
22
23
|
@requested = true
|
23
24
|
@valid ||= true if validated_request.valid?
|
25
|
+
@last_error_message = validated_request.error.exception_message unless validated_request.valid?
|
24
26
|
end
|
25
27
|
|
26
28
|
def requested?
|
@@ -14,13 +14,15 @@ module OpenapiFirst
|
|
14
14
|
def initialize(response_definition)
|
15
15
|
@response = response_definition
|
16
16
|
@responded = false
|
17
|
+
@last_error_message = nil
|
17
18
|
end
|
18
19
|
|
19
|
-
attr_reader :response
|
20
|
+
attr_reader :response, :last_error_message
|
20
21
|
|
21
22
|
def track(validated_response)
|
22
23
|
@responded = true
|
23
24
|
@valid ||= true if validated_response.valid?
|
25
|
+
@last_error_message = validated_response.error.exception_message unless validated_response.valid?
|
24
26
|
end
|
25
27
|
|
26
28
|
def responded?
|
@@ -84,7 +84,7 @@ module OpenapiFirst
|
|
84
84
|
def explain_unfinished_request(request)
|
85
85
|
return 'No requests tracked!' unless request.requested?
|
86
86
|
|
87
|
-
|
87
|
+
"All requests invalid! (#{request.last_error_message.inspect})" unless request.any_valid_request?
|
88
88
|
end
|
89
89
|
|
90
90
|
def response_label(response)
|
@@ -97,7 +97,7 @@ module OpenapiFirst
|
|
97
97
|
def explain_unfinished_response(response)
|
98
98
|
return 'No responses tracked!' unless response.responded?
|
99
99
|
|
100
|
-
|
100
|
+
"All responses invalid! (#{response.last_error_message.inspect})" unless response.any_valid_response?
|
101
101
|
end
|
102
102
|
end
|
103
103
|
end
|
@@ -13,7 +13,11 @@ module OpenapiFirst
|
|
13
13
|
Result = Data.define(:plans, :coverage)
|
14
14
|
|
15
15
|
class << self
|
16
|
-
|
16
|
+
attr_reader :current_run
|
17
|
+
|
18
|
+
def install
|
19
|
+
return if @installed
|
20
|
+
|
17
21
|
@after_request_validation = lambda do |validated_request, oad|
|
18
22
|
track_request(validated_request, oad)
|
19
23
|
end
|
@@ -26,12 +30,21 @@ module OpenapiFirst
|
|
26
30
|
config.after_request_validation(&@after_request_validation)
|
27
31
|
config.after_response_validation(&@after_response_validation)
|
28
32
|
end
|
33
|
+
@installed = true
|
34
|
+
end
|
35
|
+
|
36
|
+
def start(skip_response: nil)
|
37
|
+
@current_run = Test.definitions.values.to_h do |oad|
|
38
|
+
plan = Plan.for(oad, skip_response:)
|
39
|
+
[oad.filepath, plan]
|
40
|
+
end
|
29
41
|
end
|
30
42
|
|
31
|
-
def
|
43
|
+
def uninstall
|
32
44
|
configuration = OpenapiFirst.configuration
|
33
45
|
configuration.hooks[:after_request_validation].delete(@after_request_validation)
|
34
46
|
configuration.hooks[:after_response_validation].delete(@after_response_validation)
|
47
|
+
@installed = nil
|
35
48
|
end
|
36
49
|
|
37
50
|
# Clear current coverage run
|
@@ -51,24 +64,18 @@ module OpenapiFirst
|
|
51
64
|
Result.new(plans:, coverage:)
|
52
65
|
end
|
53
66
|
|
54
|
-
private
|
55
|
-
|
56
67
|
# Returns all plans (Plan) that were registered for this run
|
57
68
|
def plans
|
58
|
-
current_run
|
69
|
+
current_run&.values
|
59
70
|
end
|
60
71
|
|
72
|
+
private
|
73
|
+
|
61
74
|
def coverage
|
62
|
-
return 0
|
75
|
+
return 0 unless plans
|
63
76
|
|
64
77
|
plans.sum(&:coverage) / plans.length
|
65
78
|
end
|
66
|
-
|
67
|
-
def current_run
|
68
|
-
@current_run ||= Test.definitions.values.to_h do |oad|
|
69
|
-
[oad.filepath, Plan.new(oad)]
|
70
|
-
end
|
71
|
-
end
|
72
79
|
end
|
73
80
|
end
|
74
81
|
end
|
data/lib/openapi_first/test.rb
CHANGED
@@ -16,6 +16,9 @@ module OpenapiFirst
|
|
16
16
|
class Setup
|
17
17
|
def initialize
|
18
18
|
@minimum_coverage = 0
|
19
|
+
@coverage_formatter = Coverage::TerminalFormatter
|
20
|
+
@coverage_formatter_options = {}
|
21
|
+
@skip_response_coverage = nil
|
19
22
|
yield self
|
20
23
|
end
|
21
24
|
|
@@ -23,14 +26,25 @@ module OpenapiFirst
|
|
23
26
|
Test.register(*)
|
24
27
|
end
|
25
28
|
|
26
|
-
attr_accessor :minimum_coverage
|
29
|
+
attr_accessor :minimum_coverage, :coverage_formatter_options, :coverage_formatter
|
30
|
+
|
31
|
+
def skip_response_coverage(&block)
|
32
|
+
return @skip_response_coverage unless block_given?
|
33
|
+
|
34
|
+
@skip_response_coverage = block
|
35
|
+
end
|
27
36
|
|
28
37
|
# This called at_exit
|
29
38
|
def handle_exit
|
30
39
|
coverage = Coverage.result.coverage
|
31
40
|
# :nocov:
|
32
41
|
puts 'API Coverage did not detect any API requests for the registered API descriptions' if coverage.zero?
|
33
|
-
|
42
|
+
if coverage.positive?
|
43
|
+
Test.report_coverage(
|
44
|
+
formatter: coverage_formatter,
|
45
|
+
**coverage_formatter_options
|
46
|
+
)
|
47
|
+
end
|
34
48
|
return unless minimum_coverage > coverage
|
35
49
|
|
36
50
|
puts "API Coverage fails with exit 2, because API coverage of #{coverage}%" \
|
@@ -45,8 +59,9 @@ module OpenapiFirst
|
|
45
59
|
raise ArgumentError, "Please provide a block to #{self.class}.setup to register you API descriptions"
|
46
60
|
end
|
47
61
|
|
48
|
-
Coverage.
|
62
|
+
Coverage.install
|
49
63
|
setup = Setup.new(&)
|
64
|
+
Coverage.start(skip_response: setup.skip_response_coverage)
|
50
65
|
|
51
66
|
if definitions.empty?
|
52
67
|
raise NotRegisteredError,
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: openapi_first
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andreas Haller
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-
|
10
|
+
date: 2025-03-25 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: hana
|
@@ -162,5 +162,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
162
162
|
requirements: []
|
163
163
|
rubygems_version: 3.6.5
|
164
164
|
specification_version: 4
|
165
|
-
summary:
|
165
|
+
summary: OpenAPI based request validation, response validation, contract-testing and
|
166
|
+
coverage
|
166
167
|
test_files: []
|