Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .rspec
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
--format documentation
--format progress
--color
--require spec_helper
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
31 changes: 28 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,24 +70,49 @@ 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)
```
3. Run your tests. The Coverage feature will tell you about missing or invalid requests/responses.

(✷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
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/Gemfile.lock
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
2 changes: 1 addition & 1 deletion lib/openapi_first/test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
18 changes: 16 additions & 2 deletions lib/openapi_first/test/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand All @@ -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
4 changes: 2 additions & 2 deletions lib/openapi_first/test/coverage.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions lib/openapi_first/test/coverage/plan.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion lib/openapi_first/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# frozen_string_literal: true

module OpenapiFirst
VERSION = '2.9.3'
VERSION = '2.10.0'
end
18 changes: 14 additions & 4 deletions spec/test_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down