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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 131c3511b96fc2a420b9c0a1b2e4deeb5b24d8602ad6210e5a60ed1a46fe6257
4
- data.tar.gz: f6b7a44491e9aedf68498b2200a46e6048b9036360447a34c077c7476da8fc70
3
+ metadata.gz: be641d2241f6650f7eb0c9bc4a5d78552f2945ee7a030e6003fa0567cb547c85
4
+ data.tar.gz: 3a7d5911b1c8b30074068a299f8841b791b447240141ca3303814a72bb7424ec
5
5
  SHA512:
6
- metadata.gz: 498b7341aa473504c5fa13dd95e92d7d1e2317d690ddbd88fac36974c05b10cccd8ae8b9dccc025036151aab484304e14f053003668b576c547ac1c7c0716d4b
7
- data.tar.gz: 5cb776022d6e0fb684b3c30e7dd507a6591e5bc0b34bc384347a28e2b5030f20c976fa6f92b9b3e81c8d8ec6912d46e6b0ee053189267de320ccf418e7d88f4f
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 |test|
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 initialize(oad)
16
- @oad = oad
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
- add_route request_method: route.request_method,
22
- path: route.path,
23
- requests: route.requests,
24
- responses: route.responses
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
- attr_reader :filepath, :oad, :routes
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)).to_i
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
- 'All requests invalid!' unless request.any_valid_request?
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
- 'All responses invalid!' unless response.any_valid_response?
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
- def start
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 stop
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.values
69
+ current_run&.values
59
70
  end
60
71
 
72
+ private
73
+
61
74
  def coverage
62
- return 0 if plans.empty?
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
@@ -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
- Test.report_coverage if coverage.positive?
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.start
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,
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OpenapiFirst
4
- VERSION = '2.4.0'
4
+ VERSION = '2.5.0'
5
5
  end
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.0
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-02-26 00:00:00.000000000 Z
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: Implement HTTP APIs based on OpenApi 3.x
165
+ summary: OpenAPI based request validation, response validation, contract-testing and
166
+ coverage
166
167
  test_files: []