diff --git a/.rspec b/.rspec index 34c5164d..bd5e0288 100644 --- a/.rspec +++ b/.rspec @@ -1,3 +1,3 @@ ---format documentation +--format progress --color --require spec_helper diff --git a/CHANGELOG.md b/CHANGELOG.md index 233437e3..8b0e602e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,12 @@ ## Unreleased +## 2.10.0 + +- Add Test::Configuration#skip_coverage to skip test coverage for specific paths + request methods and all responses +- Deprecate setting minimum_coverage value. Use skip_response_coverage, ignored_unknown_status to configure coverage instead. - Update openapi_parameters to make parsing array query parameters more consistent. - Now parsing an empty array query parameter like `ids=&` or `ids&` both result in an empty array value (`[]`) instead of `nil` or `""`. + Now parsing empty array query parameter like `ids=&` or `ids&` both result in an empty array value (`[]`) instead of `nil` or `""`. - Fix Test::Coverage.result returning < 100 even if plan is fully covered ## 2.9.3 diff --git a/Gemfile.lock b/Gemfile.lock index 0072390f..df3a4f37 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - openapi_first (2.9.3) + openapi_first (2.10.0) hana (~> 1.3) json_schemer (>= 2.1, < 3.0) openapi_parameters (>= 0.6.1, < 2.0) diff --git a/README.md b/README.md index 3bd2cd3f..1b0315a5 100644 --- a/README.md +++ b/README.md @@ -70,9 +70,9 @@ Here is how to set it up: end ``` - Or inject a Module to wrap (prepend) the `call` method of your Rack app Class. - + NOTE: This is still work in progress. It works with basic Sinatra apps, but does not work with Hanami or Rails out of the box, yet. PRs welcome 🤗 - + ```ruby OpenapiFirst::Test.observe(MyApplication) ``` @@ -80,14 +80,39 @@ Here is how to set it up: (✷1): It does not matter what method of openapi_first you use to validate requests/responses. Instead of using `OpenapiFirstTest.app` to wrap your application, you could also use the [middlewares](#rack-middlewares) or [test assertion method](#test-assertions), but you would have to do that for all requests/responses defined in your API description to make coverage work. -OpenapiFirst' request validation raises an error when a request is not defined. You can deactivate this during testing: +### Configure test coverage + +OpenapiFirst::Test raises an error when a request is not defined. You can deactivate this with: ```ruby OpenapiFirst::Test.setup do |test| + # … test.ignore_unknown_requests = true end ``` +Exclude certain _responses_ from coverage with `skip_coverage`: + +```ruby +OpenapiFirst::Test.setup do |test| + # … + test.skip_response_coverage do |response_definition| + response_definition.status == '5XX' + end +end +``` + +Skip coverage for a request and all responses alltogether of a route with `skip_coverage`: + +```ruby +OpenapiFirst::Test.setup do |test| + # … + test.skip_coverage do |path, request_method| + path == '/bookings/{bookingId}' && requests_method == 'DELETE' + end +end +``` + ## Rack Middlewares ### Request validation diff --git a/benchmarks/Gemfile.lock b/benchmarks/Gemfile.lock index 95e29395..bf0e4a6e 100644 --- a/benchmarks/Gemfile.lock +++ b/benchmarks/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - openapi_first (2.9.3) + openapi_first (2.10.0) hana (~> 1.3) json_schemer (>= 2.1, < 3.0) openapi_parameters (>= 0.6.1, < 2.0) diff --git a/lib/openapi_first/test.rb b/lib/openapi_first/test.rb index 3c9f76b8..17149117 100644 --- a/lib/openapi_first/test.rb +++ b/lib/openapi_first/test.rb @@ -41,7 +41,7 @@ def self.setup configuration.registry.each { |name, oad| register(oad, as: name) } configuration.apps.each { |name, app| observe(app, api: name) } - Coverage.start(skip_response: configuration.skip_response_coverage) + Coverage.start(skip_response: configuration.skip_response_coverage, skip_route: configuration.skip_coverage) if definitions.empty? raise NotRegisteredError, diff --git a/lib/openapi_first/test/configuration.rb b/lib/openapi_first/test/configuration.rb index 83c71e0d..213817d1 100644 --- a/lib/openapi_first/test/configuration.rb +++ b/lib/openapi_first/test/configuration.rb @@ -9,6 +9,7 @@ def initialize @coverage_formatter = Coverage::TerminalFormatter @coverage_formatter_options = {} @skip_response_coverage = nil + @skip_coverage = nil @response_raise_error = true @ignored_unknown_status = [404] @report_coverage = true @@ -29,9 +30,9 @@ def observe(app, api: :default) @apps[api] = app end - attr_accessor :coverage_formatter_options, :coverage_formatter, :response_raise_error, :minimum_coverage, + attr_accessor :coverage_formatter_options, :coverage_formatter, :response_raise_error, :ignore_unknown_requests - attr_reader :registry, :apps, :report_coverage, :ignored_unknown_status + attr_reader :registry, :apps, :report_coverage, :ignored_unknown_status, :minimum_coverage # Configure report coverage # @param [Boolean, :warn] value Whether to report coverage or just warn. @@ -44,11 +45,24 @@ def report_coverage=(value) @report_coverage = value end + # @deprecated Use skip_response_coverage, ignored_unknown_status or skip_coverage to configure coverage + def minimum_coverage=(value) + warn 'OpenapiFirst::Test::Configuration#minimum_coverage= is deprecated. ' \ + 'Use skip_response_coverage, ignored_unknown_status to configure coverage instead.' + @minimum_coverage = value + end + def skip_response_coverage(&block) return @skip_response_coverage unless block_given? @skip_response_coverage = block end + + def skip_coverage(&block) + return @skip_coverage unless block_given? + + @skip_coverage = block + end end end end diff --git a/lib/openapi_first/test/coverage.rb b/lib/openapi_first/test/coverage.rb index 27ba5944..354fb6ee 100644 --- a/lib/openapi_first/test/coverage.rb +++ b/lib/openapi_first/test/coverage.rb @@ -19,9 +19,9 @@ class << self def install = Test.install - def start(skip_response: nil) + def start(skip_response: nil, skip_route: nil) @current_run = Test.definitions.values.to_h do |oad| - plan = Plan.for(oad, skip_response:) + plan = Plan.for(oad, skip_response:, skip_route:) [oad.key, plan] end end diff --git a/lib/openapi_first/test/coverage/plan.rb b/lib/openapi_first/test/coverage/plan.rb index 19fd96c3..10fdaf3b 100644 --- a/lib/openapi_first/test/coverage/plan.rb +++ b/lib/openapi_first/test/coverage/plan.rb @@ -12,9 +12,11 @@ module Coverage class Plan class UnknownRequestError < StandardError; end - def self.for(oad, skip_response: nil) + def self.for(oad, skip_response: nil, skip_route: nil) plan = new(definition_key: oad.key, filepath: oad.filepath) - oad.routes.each do |route| + routes = oad.routes + routes = routes.reject { |route| skip_route[route.path, route.request_method] } if skip_route + routes.each do |route| responses = skip_response ? route.responses.reject(&skip_response) : route.responses plan.add_route request_method: route.request_method, path: route.path, diff --git a/lib/openapi_first/version.rb b/lib/openapi_first/version.rb index 5e9461ce..b2d74e0f 100644 --- a/lib/openapi_first/version.rb +++ b/lib/openapi_first/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module OpenapiFirst - VERSION = '2.9.3' + VERSION = '2.10.0' end diff --git a/spec/test_spec.rb b/spec/test_spec.rb index 7a91316b..627a6a68 100644 --- a/spec/test_spec.rb +++ b/spec/test_spec.rb @@ -141,12 +141,22 @@ def call(_env) expect(described_class::Coverage.plans.first.tasks.count).to eq(2) end - it 'can skip_response_coverage' do + it 'can skip_coverage for whole routes' do described_class.setup do |test| - test.register('./examples/openapi.yaml') - test.skip_response_coverage { |res| res.status == '401' } + test.register('./spec/data/petstore.yaml') + test.skip_coverage { |path, request_method| path == '/pets' && request_method == 'POST' } end - expect(described_class::Coverage.plans.first.tasks.count).to eq(2) + route_tasks = described_class::Coverage.plans.first.routes + expect(route_tasks.map { |route| [route.path, route.request_method] }).to eq([['/pets', 'GET'], ['/pets/{petId}', 'GET']]) + end + + it 'can skip_coverage for paths' do + described_class.setup do |test| + test.register('./spec/data/petstore.yaml') + test.skip_coverage { |path| path == '/pets' } + end + route_tasks = described_class::Coverage.plans.first.routes + expect(route_tasks.map { |route| [route.path, route.request_method] }).to eq([['/pets/{petId}', 'GET']]) end it 'raises an error if no block is given' do