Skip to content

Commit ca5f0f2

Browse files
authored
Merge pull request #339 from ahx/skip-response-coverage
Add option to skip certain responses in coverage calculation
2 parents e9e5571 + 4f4e364 commit ca5f0f2

10 files changed

Lines changed: 89 additions & 39 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## Unreleased
44

55
- 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.
6+
- Add option to skip certain responses in coverage calculation
67

78
## 2.4.0
89

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,9 +149,10 @@ Here is how to set it up for RSpec in your `spec/spec_helper.rb`:
149149
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.
150150
```ruby
151151
require 'openapi_first'
152-
OpenapiFirst::Test.setup do |test|
152+
OpenapiFirst::Test.setup do |s|
153153
test.register('openapi/openapi.yaml')
154154
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' }
155156
end
156157
```
157158
2. Wrap your app with silent request / response validation. This validates all requets/responses you do during your test run. (✷1)

examples/openapi.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,5 @@ paths:
2626
properties:
2727
hello:
2828
type: string
29+
"401":
30+
description: Unauthorized

lib/openapi_first/test.rb

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,22 @@ def initialize
1818
@minimum_coverage = 0
1919
@coverage_formatter = Coverage::TerminalFormatter
2020
@coverage_formatter_options = {}
21+
@skip_response_coverage = nil
2122
yield self
2223
end
2324

2425
def register(*)
2526
Test.register(*)
2627
end
2728

28-
def skip_response_coverage_for_status(*statuses); end
29-
3029
attr_accessor :minimum_coverage, :coverage_formatter_options, :coverage_formatter
3130

31+
def skip_response_coverage(&block)
32+
return @skip_response_coverage unless block_given?
33+
34+
@skip_response_coverage = block
35+
end
36+
3237
# This called at_exit
3338
def handle_exit
3439
coverage = Coverage.result.coverage
@@ -54,8 +59,9 @@ def self.setup(&)
5459
raise ArgumentError, "Please provide a block to #{self.class}.setup to register you API descriptions"
5560
end
5661

57-
Coverage.start
62+
Coverage.install
5863
setup = Setup.new(&)
64+
Coverage.start(skip_response: setup.skip_response_coverage)
5965

6066
if definitions.empty?
6167
raise NotRegisteredError,

lib/openapi_first/test/coverage.rb

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@ module Coverage
1313
Result = Data.define(:plans, :coverage)
1414

1515
class << self
16-
def start
16+
attr_reader :current_run
17+
18+
def install
19+
return if @installed
20+
1721
@after_request_validation = lambda do |validated_request, oad|
1822
track_request(validated_request, oad)
1923
end
@@ -26,12 +30,21 @@ def start
2630
config.after_request_validation(&@after_request_validation)
2731
config.after_response_validation(&@after_response_validation)
2832
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
2941
end
3042

31-
def stop
43+
def uninstall
3244
configuration = OpenapiFirst.configuration
3345
configuration.hooks[:after_request_validation].delete(@after_request_validation)
3446
configuration.hooks[:after_response_validation].delete(@after_response_validation)
47+
@installed = nil
3548
end
3649

3750
# Clear current coverage run
@@ -51,24 +64,18 @@ def result
5164
Result.new(plans:, coverage:)
5265
end
5366

54-
private
55-
5667
# Returns all plans (Plan) that were registered for this run
5768
def plans
58-
current_run.values
69+
current_run&.values
5970
end
6071

72+
private
73+
6174
def coverage
62-
return 0 if plans.empty?
75+
return 0 unless plans
6376

6477
plans.sum(&:coverage) / plans.length
6578
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
7279
end
7380
end
7481
end

lib/openapi_first/test/coverage/plan.rb

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,25 @@ module Coverage
1212
class Plan
1313
class UnknownRequestError < StandardError; end
1414

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)
2017
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:
2523
end
24+
plan
2625
end
2726

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
2934
private attr_reader :index
3035

3136
def track_request(validated_request)
@@ -52,8 +57,6 @@ def tasks
5257
index.values
5358
end
5459

55-
private
56-
5760
def add_route(request_method:, path:, requests:, responses:)
5861
request_tasks = requests.to_a.map do |request|
5962
index[request.key] = RequestTask.new(request)

spec/spec_helper.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@
2727
c.syntax = :expect
2828
end
2929

30-
config.after do
30+
config.after(:each) do
3131
OpenapiFirst::Test.definitions.clear
32+
OpenapiFirst::Test::Coverage.uninstall
3233
end
3334
end

spec/test/coverage/plan_spec.rb

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@
6767
oad.validate_response(request, response)
6868
end
6969

70-
subject(:plan) { described_class.new(oad) }
70+
subject(:plan) { described_class.for(oad) }
7171

7272
it 'tracks requests and responses' do
7373
request = plan.routes.first.requests.first
@@ -137,4 +137,20 @@
137137
expect(plan.tasks[1].status).to eq('200')
138138
expect(plan.tasks[2].status).to eq('4XX')
139139
end
140+
141+
context 'with skip_response option' do
142+
let(:plan) do
143+
skip_response = ->(response) { response.status == '4XX' }
144+
described_class.for(oad, skip_response:)
145+
end
146+
147+
it 'can be done without the skipped response' do
148+
expect(plan).not_to be_done
149+
150+
plan.track_request(valid_request)
151+
plan.track_response(valid_response)
152+
153+
expect(plan.coverage).to eq(100)
154+
end
155+
end
140156
end

spec/test/coverage_spec.rb

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@
55
let(:definition) { OpenapiFirst.load(filepath) }
66

77
before(:each) do
8+
described_class.install
89
OpenapiFirst::Test.register(filepath)
910
described_class.start
1011
end
1112

1213
after(:each) do
13-
described_class.stop
14+
described_class.uninstall
1415
described_class.reset
1516
end
1617

@@ -22,18 +23,22 @@
2223

2324
let(:result) { described_class.result }
2425

25-
describe '.start' do
26-
after { described_class.stop }
27-
26+
describe '.install' do
2827
it 'installs global hooks' do
28+
described_class.install
29+
2930
hooks = OpenapiFirst.configuration.hooks
30-
described_class.stop
31-
expect(hooks[:after_request_validation]).to be_empty
32-
expect(hooks[:after_response_validation]).to be_empty
33-
described_class.start
3431
expect(hooks[:after_request_validation]).not_to be_empty
3532
expect(hooks[:after_response_validation]).not_to be_empty
3633
end
34+
35+
it 'does not install hooks multiple times' do
36+
2.times { described_class.install }
37+
38+
hooks = OpenapiFirst.configuration.hooks
39+
expect(hooks[:after_request_validation].count).to eq(1)
40+
expect(hooks[:after_response_validation].count).to eq(1)
41+
end
3742
end
3843

3944
describe '.result' do

spec/test_spec.rb

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
RSpec.describe OpenapiFirst::Test do
66
after(:each) do
77
described_class.definitions.clear
8-
OpenapiFirst::Test::Coverage.stop
8+
OpenapiFirst::Test::Coverage.uninstall
99
OpenapiFirst::Test::Coverage.reset
1010
end
1111

@@ -51,6 +51,14 @@
5151
expect(described_class.definitions[:default].filepath).to eq(OpenapiFirst.load('./examples/openapi.yaml').filepath)
5252
end
5353

54+
it 'can skip responses for coverage' do
55+
described_class.setup do |test|
56+
test.register('./examples/openapi.yaml')
57+
test.skip_response_coverage { |res| res.status == '401' }
58+
end
59+
expect(described_class::Coverage.plans.first.tasks.count).to eq(2)
60+
end
61+
5462
it 'raises an error if no block is given' do
5563
expect do
5664
described_class.setup

0 commit comments

Comments
 (0)