openapi_first 2.7.4 → 2.8.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 +11 -0
- data/README.md +38 -21
- data/lib/openapi_first/configuration.rb +1 -0
- data/lib/openapi_first/definition.rb +9 -8
- data/lib/openapi_first/response_body_parsers.rb +3 -1
- data/lib/openapi_first/test/callable.rb +25 -0
- data/lib/openapi_first/test/configuration.rb +52 -0
- data/lib/openapi_first/test/coverage/terminal_formatter.rb +9 -3
- data/lib/openapi_first/test/coverage.rb +7 -26
- data/lib/openapi_first/test/observe.rb +26 -0
- data/lib/openapi_first/test/registry.rb +44 -0
- data/lib/openapi_first/test.rb +73 -80
- data/lib/openapi_first/validators/request_body.rb +1 -1
- data/lib/openapi_first/version.rb +1 -1
- metadata +5 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d5a5311b4ca774b9a0cb80e85f29243f46e1c5c762241b015090834971373a7b
|
4
|
+
data.tar.gz: 640be1c8c02b8a86b5c9d562b7ac837b73881473c54cf9ea731779352e0cee2d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 42ab4483c694d921c68fcb30099ecd779e789c5bfc9163f97a70327f5ef1aacdc7b7abae66c70341e2edd4ff5821d4fe6e9ab3e0d65b081963685272d094e5e2
|
7
|
+
data.tar.gz: 7bbc02b4e061a012663b115758b1f43a6fd4642687d07711d90e7032a800fedb453e3b32a762658bd5185d9cea0f8859b2436d28f4bea12f0bebd4bb613aeeff
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,17 @@
|
|
2
2
|
|
3
3
|
## Unreleased
|
4
4
|
|
5
|
+
## 2.8.0
|
6
|
+
|
7
|
+
### OpenapiFirst::Test is now stricter and more configurable
|
8
|
+
|
9
|
+
Changes:
|
10
|
+
- Changed OpenapiFirst::Test to raises an "invalid response" error if it sees an invalid response (https://github.com/ahx/openapi_first/issues/366).
|
11
|
+
You can change this back to the old behavior by setting `OpenapiFirst::Test::Configuration#response_raise_error = false` (but you shouldn't).
|
12
|
+
- Added `Test.setup { it.observe(MyApp) }`, `Test.observe(App, api: :my_api)` and internal `Test::Callable[]` to inject request/response validation in rack app as an alternative to overwrite the `app` method in a test
|
13
|
+
- Added `Test::Configuration#ignored_unknown_status` to configure response status(es) that do not have to be descriped in the API description. 404 statuses are ignored by default.
|
14
|
+
- Changed `OpenapiFirst::Test` to make tests fail if API description is not covered by tests. You can adapt this behavior via `OpenapiFirst::Test.setup` / `skip_response_coverage` or deactivate coverage with `OpenapiFirst::Test::Configuration#report_coverage = false` or `report_coverage = :warn`
|
15
|
+
|
5
16
|
## 2.7.4
|
6
17
|
|
7
18
|
- Return 400 if Rack cannot parse query string instead of raising an exception. Fixes https://github.com/ahx/openapi_first/issues/372
|
data/README.md
CHANGED
@@ -1,8 +1,24 @@
|
|
1
1
|
# openapi_first
|
2
2
|
|
3
|
-
openapi_first is a Ruby gem for request / response validation and contract-testing against an [OpenAPI](https://www.openapis.org/) 3.0 or 3.1 API description. It makes an APIFirst workflow easy and reliable.
|
3
|
+
openapi_first is a Ruby gem for request / response validation and contract-testing against an [OpenAPI](https://www.openapis.org/) 3.0 or 3.1 Openapi API description (OAD). It makes an APIFirst workflow easy and reliable.
|
4
4
|
|
5
|
-
|
5
|
+
## Usage
|
6
|
+
|
7
|
+
Use an OAD to validate incoming requests in production:
|
8
|
+
```ruby
|
9
|
+
use OpenapiFirst::Middlewares::RequestValidation, 'openapi/openapi.yaml'
|
10
|
+
```
|
11
|
+
|
12
|
+
Turn your request tests into contract tests against an OAD:
|
13
|
+
```ruby
|
14
|
+
# spec_helper.rb
|
15
|
+
require 'openapi_first'
|
16
|
+
OpenapiFirst::Test.setup do |config|
|
17
|
+
config.register('openapi/openapi.yaml')
|
18
|
+
end
|
19
|
+
require 'application' # Load Application code
|
20
|
+
OpenapiFirst::Test.observe(Application)
|
21
|
+
```
|
6
22
|
|
7
23
|
## Contents
|
8
24
|
|
@@ -35,27 +51,28 @@ Here is how to set it up:
|
|
35
51
|
This should go at the top of your test helper file before loading your application code.
|
36
52
|
```ruby
|
37
53
|
require 'openapi_first'
|
38
|
-
OpenapiFirst::Test.setup do |
|
39
|
-
|
40
|
-
# Optional: Make tests fail if coverage is below minimum
|
41
|
-
test.minimum_coverage = 100
|
42
|
-
# Optional: Skip certain responses, which are described in your API description, but need no test coverage
|
43
|
-
test.skip_response_coverage { it.status == '500' } #
|
44
|
-
end
|
45
|
-
```
|
46
|
-
2. Add an `app` method to your tests by including a Module. This `app` method wraps your application with silent request / response validation. This validates all requests/responses in your test run. (✷1)
|
47
|
-
```ruby
|
48
|
-
RSpec.configure do |config|
|
49
|
-
config.include OpenapiFirst::Test::Methods[MyApp], type: :request
|
50
|
-
end
|
51
|
-
```
|
52
|
-
Or add the `app` method yourself:
|
53
|
-
|
54
|
-
```ruby
|
55
|
-
def app
|
56
|
-
OpenapiFirst::Test.app(MyApp)
|
54
|
+
OpenapiFirst::Test.setup do |config|
|
55
|
+
config.register('openapi/openapi.yaml')
|
57
56
|
end
|
58
57
|
```
|
58
|
+
2. Observe your application. You can do this in one of two ways:
|
59
|
+
- Inject a Module to wrap (prepend) the `call` method of your Rack app Class.
|
60
|
+
```ruby
|
61
|
+
OpenapiFirst::Test.observe(MyApplication)
|
62
|
+
```
|
63
|
+
- Or add an `app` method to your tests, which wraps your application with silent request / response validation. (✷1)
|
64
|
+
```ruby
|
65
|
+
RSpec.configure do |config|
|
66
|
+
config.include OpenapiFirst::Test::Methods[MyApp], type: :request
|
67
|
+
end
|
68
|
+
```
|
69
|
+
Or add the `app` method yourself:
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
def app
|
73
|
+
OpenapiFirst::Test.app(MyApp)
|
74
|
+
end
|
75
|
+
```
|
59
76
|
3. Run your tests. The Coverage feature will tell you about missing or invalid requests/responses.
|
60
77
|
|
61
78
|
(✷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.
|
@@ -86,14 +86,15 @@ module OpenapiFirst
|
|
86
86
|
|
87
87
|
response_match = route.match_response(status: rack_response.status, content_type: rack_response.content_type)
|
88
88
|
error = response_match.error
|
89
|
-
if error
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
89
|
+
validated = if error
|
90
|
+
ValidatedResponse.new(rack_response, error:)
|
91
|
+
else
|
92
|
+
response_match.response.validate(rack_response)
|
93
|
+
end
|
94
|
+
@config.hooks[:after_response_validation]&.each { |hook| hook.call(validated, rack_request, self) }
|
95
|
+
raise validated.error.exception(validated) if raise_error && validated.invalid?
|
96
|
+
|
97
|
+
validated
|
97
98
|
end
|
98
99
|
|
99
100
|
private
|
@@ -23,7 +23,9 @@ module OpenapiFirst
|
|
23
23
|
register(/json/i, lambda do |body|
|
24
24
|
JSON.parse(body)
|
25
25
|
rescue JSON::ParserError
|
26
|
-
Failure.fail!(:invalid_response_body, message: '
|
26
|
+
return Failure.fail!(:invalid_response_body, message: 'JSON response body must not be empty') if body.empty?
|
27
|
+
|
28
|
+
Failure.fail!(:invalid_response_body, message: 'Failed to parse response body as JSON')
|
27
29
|
end)
|
28
30
|
end
|
29
31
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OpenapiFirst
|
4
|
+
module Test
|
5
|
+
# Return a Module with a call method that wrapps silent request/response validation to monitor a Rack app
|
6
|
+
# This is used by Openapi::Test.observe
|
7
|
+
module Callable
|
8
|
+
# Returns a Module with a `call(env)` method that wraps super inside silent request/response validation
|
9
|
+
# You can use this like `Application.prepend(OpenapiFirst::Test.app_module)` to monitor your app during testing.
|
10
|
+
def self.[](definition)
|
11
|
+
Module.new.tap do |mod|
|
12
|
+
mod.define_method(:call) do |env|
|
13
|
+
request = Rack::Request.new(env)
|
14
|
+
|
15
|
+
definition.validate_request(request, raise_error: false)
|
16
|
+
response = super(env)
|
17
|
+
status, headers, body = response
|
18
|
+
definition.validate_response(request, Rack::Response[status, headers, body], raise_error: false)
|
19
|
+
response
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OpenapiFirst
|
4
|
+
module Test
|
5
|
+
# Helper class to setup tests
|
6
|
+
class Configuration
|
7
|
+
def initialize
|
8
|
+
@minimum_coverage = 100
|
9
|
+
@coverage_formatter = Coverage::TerminalFormatter
|
10
|
+
@coverage_formatter_options = {}
|
11
|
+
@skip_response_coverage = nil
|
12
|
+
@response_raise_error = true
|
13
|
+
@ignored_unknown_status = [404]
|
14
|
+
@report_coverage = true
|
15
|
+
@registry = {}
|
16
|
+
@apps = {}
|
17
|
+
end
|
18
|
+
|
19
|
+
# Register OADs, but don't load them just yet
|
20
|
+
# @param [OpenapiFirst::OAD] oad The OAD to register
|
21
|
+
# @param [Symbol] as The name to register the OAD under
|
22
|
+
def register(oad, as: :default)
|
23
|
+
@registry[as] = oad
|
24
|
+
end
|
25
|
+
|
26
|
+
# Observe a rack app
|
27
|
+
def observe(app, api: :default)
|
28
|
+
@apps[api] = app
|
29
|
+
end
|
30
|
+
|
31
|
+
attr_accessor :coverage_formatter_options, :coverage_formatter, :response_raise_error, :minimum_coverage
|
32
|
+
attr_reader :registry, :apps, :report_coverage, :ignored_unknown_status
|
33
|
+
|
34
|
+
# Configure report coverage
|
35
|
+
# @param [Boolean, :warn] value Whether to report coverage or just warn.
|
36
|
+
def report_coverage=(value)
|
37
|
+
allowed_values = [true, false, :warn]
|
38
|
+
unless allowed_values.include?(value)
|
39
|
+
raise ArgumentError, "'report_coverage' must be one of #{allowed_values}, but was #{value.inspect}"
|
40
|
+
end
|
41
|
+
|
42
|
+
@report_coverage = value
|
43
|
+
end
|
44
|
+
|
45
|
+
def skip_response_coverage(&block)
|
46
|
+
return @skip_response_coverage unless block_given?
|
47
|
+
|
48
|
+
@skip_response_coverage = block
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -10,10 +10,14 @@ module OpenapiFirst
|
|
10
10
|
@focused = focused && !verbose
|
11
11
|
end
|
12
12
|
|
13
|
-
# This takes a list of Coverage::Plan instances and outputs a String
|
14
13
|
def format(coverage_result)
|
14
|
+
coverage = coverage_result.coverage
|
15
15
|
@out = StringIO.new
|
16
|
-
|
16
|
+
if coverage.zero?
|
17
|
+
@out.puts 'API Coverage did not detect any API requests for the registered API descriptions. ' \
|
18
|
+
'Make sure to observe your application using OpenapiFirst::Test.'
|
19
|
+
end
|
20
|
+
coverage_result.plans.each { |plan| format_plan(plan) } if coverage.positive?
|
17
21
|
@out.string
|
18
22
|
end
|
19
23
|
|
@@ -85,7 +89,9 @@ module OpenapiFirst
|
|
85
89
|
def explain_unfinished_request(request)
|
86
90
|
return 'No requests tracked!' unless request.requested?
|
87
91
|
|
88
|
-
|
92
|
+
return if request.any_valid_request?
|
93
|
+
|
94
|
+
"All requests invalid! (#{request.last_error_message.inspect})"
|
89
95
|
end
|
90
96
|
|
91
97
|
def response_label(response)
|
@@ -12,26 +12,12 @@ module OpenapiFirst
|
|
12
12
|
|
13
13
|
Result = Data.define(:plans, :coverage)
|
14
14
|
|
15
|
+
@current_run = {}
|
16
|
+
|
15
17
|
class << self
|
16
18
|
attr_reader :current_run
|
17
19
|
|
18
|
-
def install
|
19
|
-
return if @installed
|
20
|
-
|
21
|
-
@after_request_validation = lambda do |validated_request, oad|
|
22
|
-
track_request(validated_request, oad)
|
23
|
-
end
|
24
|
-
|
25
|
-
@after_response_validation = lambda do |validated_response, request, oad|
|
26
|
-
track_response(validated_response, request, oad)
|
27
|
-
end
|
28
|
-
|
29
|
-
OpenapiFirst.configure do |config|
|
30
|
-
config.after_request_validation(&@after_request_validation)
|
31
|
-
config.after_response_validation(&@after_response_validation)
|
32
|
-
end
|
33
|
-
@installed = true
|
34
|
-
end
|
20
|
+
def install = Test.install
|
35
21
|
|
36
22
|
def start(skip_response: nil)
|
37
23
|
@current_run = Test.definitions.values.to_h do |oad|
|
@@ -40,16 +26,11 @@ module OpenapiFirst
|
|
40
26
|
end
|
41
27
|
end
|
42
28
|
|
43
|
-
def uninstall
|
44
|
-
configuration = OpenapiFirst.configuration
|
45
|
-
configuration.hooks[:after_request_validation].delete(@after_request_validation)
|
46
|
-
configuration.hooks[:after_response_validation].delete(@after_response_validation)
|
47
|
-
@installed = nil
|
48
|
-
end
|
29
|
+
def uninstall = Test.uninstall
|
49
30
|
|
50
31
|
# Clear current coverage run
|
51
32
|
def reset
|
52
|
-
@current_run =
|
33
|
+
@current_run = {}
|
53
34
|
end
|
54
35
|
|
55
36
|
def track_request(request, oad)
|
@@ -66,13 +47,13 @@ module OpenapiFirst
|
|
66
47
|
|
67
48
|
# Returns all plans (Plan) that were registered for this run
|
68
49
|
def plans
|
69
|
-
current_run
|
50
|
+
current_run.values
|
70
51
|
end
|
71
52
|
|
72
53
|
private
|
73
54
|
|
74
55
|
def coverage
|
75
|
-
return 0
|
56
|
+
return 0 if plans.empty?
|
76
57
|
|
77
58
|
plans.sum(&:coverage) / plans.length
|
78
59
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OpenapiFirst
|
4
|
+
module Test
|
5
|
+
class ObserveError < Error; end
|
6
|
+
|
7
|
+
# @visible private
|
8
|
+
module Observed; end
|
9
|
+
|
10
|
+
# Inject silent request/response validation to observe rack apps during testing
|
11
|
+
module Observe
|
12
|
+
def self.observe(app, api: :default)
|
13
|
+
unless app.instance_methods.include?(:call)
|
14
|
+
raise ObserveError, "Don't know how to observe #{app}, because it has no call instance method."
|
15
|
+
end
|
16
|
+
|
17
|
+
return if app.include?(Observed)
|
18
|
+
|
19
|
+
definition = OpenapiFirst::Test[api]
|
20
|
+
mod = OpenapiFirst::Test::Callable[definition]
|
21
|
+
app.prepend(mod)
|
22
|
+
app.include(Observed)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OpenapiFirst
|
4
|
+
module Test
|
5
|
+
class NotRegisteredError < Error; end
|
6
|
+
class AlreadyRegisteredError < Error; end
|
7
|
+
|
8
|
+
# @visibility private
|
9
|
+
module Registry
|
10
|
+
def definitions
|
11
|
+
@definitions ||= {}
|
12
|
+
end
|
13
|
+
|
14
|
+
# Register an OpenAPI definition for testing
|
15
|
+
# @param path_or_definition [String, Definition] Path to the OpenAPI file or a Definition object
|
16
|
+
# @param as [Symbol] Name to register the API definition as
|
17
|
+
def register(path_or_definition, as: :default)
|
18
|
+
if definitions.key?(as) && as == :default
|
19
|
+
raise(
|
20
|
+
AlreadyRegisteredError,
|
21
|
+
"#{definitions[as].filepath.inspect} is already registered " \
|
22
|
+
"as ':default' so you cannot register #{path_or_definition.inspect} without " \
|
23
|
+
'giving it a custom name. Please call register with a custom key like: ' \
|
24
|
+
"#{name}.register(#{path_or_definition.inspect}, as: :my_other_api)"
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
definition = OpenapiFirst.load(path_or_definition)
|
29
|
+
definitions[as] = definition
|
30
|
+
definition
|
31
|
+
end
|
32
|
+
|
33
|
+
def [](api)
|
34
|
+
definitions.fetch(api) do
|
35
|
+
option = api == :default ? '' : ", as: #{api.inspect}"
|
36
|
+
raise(NotRegisteredError,
|
37
|
+
"API description '#{api.inspect}' not found." \
|
38
|
+
"Please call #{name}.register('myopenapi.yaml'#{option}) " \
|
39
|
+
'once before running tests.')
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/openapi_first/test.rb
CHANGED
@@ -1,10 +1,23 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'test/configuration'
|
4
|
+
require_relative 'test/registry'
|
5
|
+
|
3
6
|
module OpenapiFirst
|
4
7
|
# Test integration
|
5
8
|
module Test
|
6
9
|
autoload :Coverage, 'openapi_first/test/coverage'
|
7
10
|
autoload :Methods, 'openapi_first/test/methods'
|
11
|
+
autoload :Callable, 'openapi_first/test/callable'
|
12
|
+
autoload :Observe, 'openapi_first/test/observe'
|
13
|
+
extend Registry
|
14
|
+
|
15
|
+
class CoverageError < Error; end
|
16
|
+
|
17
|
+
# Inject request/response validation in a rack app class
|
18
|
+
def self.observe(app, api: :default)
|
19
|
+
Observe.observe(app, api:)
|
20
|
+
end
|
8
21
|
|
9
22
|
def self.minitest?(base)
|
10
23
|
base.include?(::Minitest::Assertions)
|
@@ -12,58 +25,23 @@ module OpenapiFirst
|
|
12
25
|
false
|
13
26
|
end
|
14
27
|
|
15
|
-
|
16
|
-
|
17
|
-
def initialize
|
18
|
-
@minimum_coverage = 0
|
19
|
-
@coverage_formatter = Coverage::TerminalFormatter
|
20
|
-
@coverage_formatter_options = {}
|
21
|
-
@skip_response_coverage = nil
|
22
|
-
yield self
|
23
|
-
end
|
24
|
-
|
25
|
-
def register(oad, as: :default)
|
26
|
-
Test.register(oad, as:)
|
27
|
-
end
|
28
|
-
|
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
|
36
|
-
|
37
|
-
# This called at_exit
|
38
|
-
def handle_exit
|
39
|
-
coverage = Coverage.result.coverage
|
40
|
-
# :nocov:
|
41
|
-
puts 'API Coverage did not detect any API requests for the registered API descriptions' if coverage.zero?
|
42
|
-
if coverage.positive?
|
43
|
-
Test.report_coverage(
|
44
|
-
formatter: coverage_formatter,
|
45
|
-
**coverage_formatter_options
|
46
|
-
)
|
47
|
-
end
|
48
|
-
return unless minimum_coverage > coverage
|
49
|
-
|
50
|
-
puts "API Coverage fails with exit 2, because API coverage of #{coverage}% " \
|
51
|
-
"is below minimum of #{minimum_coverage}%!"
|
52
|
-
exit 2
|
53
|
-
# :nocov:
|
54
|
-
end
|
28
|
+
def self.configuration
|
29
|
+
@configuration ||= Configuration.new
|
55
30
|
end
|
56
31
|
|
57
32
|
# Sets up OpenAPI test coverage and OAD registration.
|
58
|
-
# @yieldparam [OpenapiFirst::Test::
|
59
|
-
def self.setup
|
33
|
+
# @yieldparam [OpenapiFirst::Test::Configuration] configuration A configuration to setup test integration
|
34
|
+
def self.setup
|
60
35
|
unless block_given?
|
61
|
-
raise ArgumentError, "Please provide a block to #{self.class}.
|
36
|
+
raise ArgumentError, "Please provide a block to #{self.class}.confgure to register you API descriptions"
|
62
37
|
end
|
63
38
|
|
64
|
-
|
65
|
-
|
66
|
-
|
39
|
+
install
|
40
|
+
yield configuration
|
41
|
+
|
42
|
+
configuration.registry.each { |name, oad| register(oad, as: name) }
|
43
|
+
configuration.apps.each { |name, app| observe(app, api: name) }
|
44
|
+
Coverage.start(skip_response: configuration.skip_response_coverage)
|
67
45
|
|
68
46
|
if definitions.empty?
|
69
47
|
raise NotRegisteredError,
|
@@ -72,17 +50,37 @@ module OpenapiFirst
|
|
72
50
|
"OpenapiFirst::Test.setup { |test| test.register('myopenapi.yaml') }"
|
73
51
|
end
|
74
52
|
|
53
|
+
@exit_handler = method(:handle_exit)
|
54
|
+
|
75
55
|
@setup ||= at_exit do
|
76
|
-
|
56
|
+
# :nocov:
|
57
|
+
@exit_handler&.call
|
58
|
+
# :nocov:
|
77
59
|
end
|
78
60
|
end
|
79
61
|
|
62
|
+
def self.handle_exit
|
63
|
+
return unless configuration.report_coverage
|
64
|
+
|
65
|
+
report_coverage(
|
66
|
+
formatter: configuration.coverage_formatter,
|
67
|
+
**configuration.coverage_formatter_options
|
68
|
+
)
|
69
|
+
return unless configuration.report_coverage == true
|
70
|
+
|
71
|
+
coverage = Coverage.result.coverage
|
72
|
+
return if coverage >= configuration.minimum_coverage
|
73
|
+
|
74
|
+
puts "API Coverage fails with exit 2, because not all described requests and responses have been tested (#{coverage.round(4)}% covered)." # rubocop:disable Layout/LineLength
|
75
|
+
|
76
|
+
exit 2
|
77
|
+
end
|
78
|
+
|
80
79
|
# Print the coverage report
|
81
80
|
# @param formatter A formatter to define the report.
|
82
81
|
# @output [IO] An output where to puts the report.
|
83
82
|
def self.report_coverage(formatter: Coverage::TerminalFormatter, **)
|
84
|
-
|
85
|
-
puts formatter.new(**).format(coverage_result)
|
83
|
+
puts formatter.new(**).format(Coverage.result)
|
86
84
|
end
|
87
85
|
|
88
86
|
# Returns the Rack app wrapped with silent request, response validation
|
@@ -97,42 +95,37 @@ module OpenapiFirst
|
|
97
95
|
end
|
98
96
|
end
|
99
97
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
attr_reader :definitions
|
107
|
-
|
108
|
-
# Register an OpenAPI definition for testing
|
109
|
-
# @param path_or_definition [String, Definition] Path to the OpenAPI file or a Definition object
|
110
|
-
# @param as [Symbol] Name to register the API definition as
|
111
|
-
def register(path_or_definition, as: :default)
|
112
|
-
if definitions.key?(as) && as == :default
|
113
|
-
raise(
|
114
|
-
AlreadyRegisteredError,
|
115
|
-
"#{definitions[as].filepath.inspect} is already registered " \
|
116
|
-
"as ':default' so you cannot register #{path_or_definition.inspect} without " \
|
117
|
-
'giving it a custom name. Please call register with a custom key like: ' \
|
118
|
-
"OpenapiFirst::Test.register(#{path_or_definition.inspect}, as: :my_other_api)"
|
119
|
-
)
|
98
|
+
def self.install
|
99
|
+
return if @installed
|
100
|
+
|
101
|
+
OpenapiFirst.configure do |config|
|
102
|
+
@after_request_validation = config.after_request_validation do |validated_request, oad|
|
103
|
+
Coverage.track_request(validated_request, oad)
|
120
104
|
end
|
121
105
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
106
|
+
@after_response_validation = config.after_response_validation do |validated_response, rack_request, oad|
|
107
|
+
if validated_response.invalid? && raise_response_error?(validated_response)
|
108
|
+
raise validated_response.error.exception
|
109
|
+
end
|
126
110
|
|
127
|
-
|
128
|
-
definitions.fetch(api) do
|
129
|
-
option = api == :default ? '' : ", as: #{api.inspect}"
|
130
|
-
raise(NotRegisteredError,
|
131
|
-
"API description '#{api.inspect}' not found." \
|
132
|
-
"Please call OpenapiFirst::Test.register('myopenapi.yaml'#{option}) " \
|
133
|
-
'once before running tests.')
|
111
|
+
Coverage.track_response(validated_response, rack_request, oad)
|
134
112
|
end
|
135
113
|
end
|
114
|
+
@installed = true
|
115
|
+
end
|
116
|
+
|
117
|
+
def self.raise_response_error?(validated_response)
|
118
|
+
configuration.response_raise_error && !configuration.ignored_unknown_status.include?(validated_response.status)
|
119
|
+
end
|
120
|
+
|
121
|
+
def self.uninstall
|
122
|
+
configuration = OpenapiFirst.configuration
|
123
|
+
configuration.hooks[:after_request_validation].delete(@after_request_validation)
|
124
|
+
configuration.hooks[:after_response_validation].delete(@after_response_validation)
|
125
|
+
definitions.clear
|
126
|
+
@configuration = nil
|
127
|
+
@installed = nil
|
128
|
+
@exit_handler = nil
|
136
129
|
end
|
137
130
|
end
|
138
131
|
end
|
@@ -11,7 +11,7 @@ module OpenapiFirst
|
|
11
11
|
def call(parsed_request)
|
12
12
|
body = parsed_request.body
|
13
13
|
if body.nil?
|
14
|
-
Failure.fail!(:invalid_body, message: 'Request body
|
14
|
+
Failure.fail!(:invalid_body, message: 'Request body must not be empty') if @required
|
15
15
|
return
|
16
16
|
end
|
17
17
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: openapi_first
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andreas Haller
|
@@ -123,6 +123,8 @@ files:
|
|
123
123
|
- lib/openapi_first/schema/validation_error.rb
|
124
124
|
- lib/openapi_first/schema/validation_result.rb
|
125
125
|
- lib/openapi_first/test.rb
|
126
|
+
- lib/openapi_first/test/callable.rb
|
127
|
+
- lib/openapi_first/test/configuration.rb
|
126
128
|
- lib/openapi_first/test/coverage.rb
|
127
129
|
- lib/openapi_first/test/coverage/plan.rb
|
128
130
|
- lib/openapi_first/test/coverage/request_task.rb
|
@@ -131,7 +133,9 @@ files:
|
|
131
133
|
- lib/openapi_first/test/coverage/terminal_formatter.rb
|
132
134
|
- lib/openapi_first/test/methods.rb
|
133
135
|
- lib/openapi_first/test/minitest_helpers.rb
|
136
|
+
- lib/openapi_first/test/observe.rb
|
134
137
|
- lib/openapi_first/test/plain_helpers.rb
|
138
|
+
- lib/openapi_first/test/registry.rb
|
135
139
|
- lib/openapi_first/validated_request.rb
|
136
140
|
- lib/openapi_first/validated_response.rb
|
137
141
|
- lib/openapi_first/validators/request_body.rb
|